使用ViewModel和LiveData实现Android搜索

10

我正在为Udacity课程做一个安卓项目,目前正试图实现搜索功能,同时遵循安卓架构组件并使用Firestore和Room。对于所有这些概念,我都相当新手,请指出任何看起来不对的地方。

因此,我制作了一个数据库仓库来同步我的Firestore和Room数据库,并提供数据。然后,我使用ViewModel和观察者模式(我认为是这样)让我的观察者获取数据并查找更改,将其传递给我的适配器(refreshMyList(List)),该适配器像这样填充recyclerview:

 contactViewModel = ViewModelProviders.of(this).get(ContactsViewModel.class);
 contactViewModel.getAllContacts().observe(this, new 
 Observer<List<DatabaseContacts>>() {
        @Override
        public void onChanged(@Nullable List<DatabaseContacts> 
        databaseContacts) {
            ArrayList<DatabaseContacts> tempList = new ArrayList<>();
            tempList.addAll(databaseContacts);
            contactsAdapter.refreshMyList(tempList);
            if (tempList.size() < 1) {
                results.setVisibility(View.VISIBLE);
            } else {
                results.setVisibility(View.GONE);
            }
        }
    });

我现在想对数据进行搜索,我的房间查询都已经设置好了,而且我在我的数据存储库中有基于搜索字符串获取联系人的方法,但是我的列表无法刷新。我读到有一些方法可以做到,比如Transformations.switchMap?但是我似乎无法理解它的工作原理,有人能帮我吗。

目前我正在尝试从异步任务返回结果列表,它以前会返回实时数据,但我将其更改为getValue()始终为null,不确定是否正确,这是异步内容:

private static class searchContactByName extends AsyncTask<String, Void, 
ArrayList<DatabaseContacts>> {

    private LiveDatabaseContactsDao mDao;

    searchContactByName(LiveDatabaseContactsDao dao){
        this.mDao = dao;
    }

    @Override
    protected ArrayList<DatabaseContacts> doInBackground(String... params) {
        ArrayList<DatabaseContacts> contactsArrayList = new ArrayList<>();
        mDao.findByName("%" + params[0] + "%");
        return contactsArrayList;
    }
}

我从我的联系人库中调用这个函数,并将其包装在自己的封装器中:

我从我的联系人库中调用这个函数,并将其包装在自己的封装器中:

public List<DatabaseContacts> getContactByName(String name) throws 
ExecutionException, InterruptedException {
    //return databaseContactsDao.findByName(name);
    return new searchContactByName(databaseContactsDao).execute(name).get();
}

我在我的视图模型中这样调用它:

而这被称为:

public List<DatabaseContacts> getContactByName(String name) throws 
ExecutionException, InterruptedException {
    return  contactRepository.getContactByName(name);
}

然后我从我的片段中调用它:

private void searchDatabase(String searchString) throws ExecutionException, 
InterruptedException {
    List<DatabaseContacts> searchedContacts = 
    contactViewModel.getContactByName("%" + searchString + "%");
    ArrayList<DatabaseContacts> contactsArrayList = new ArrayList<>();
    if (searchedContacts !=  null){
        contactsArrayList.addAll(searchedContacts);
        contactsAdapter.refreshMyList(contactsArrayList);
    }
}

并且这是从我的onCreateOptionsMenu中的on search query text changed方法调用的:

        @Override
        public boolean onQueryTextChange(String newText) {
            try {
                searchDatabase(newText);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return false;
        }

但它什么也没做,我的原始recyclerview内容从未改变,有什么想法吗?

5个回答

12

你可以使用Transformation.switchMap来进行搜索操作。

  1. 在ViewModel中创建一个MutableLiveData,它包含最新的搜索字符串。

  2. 在ViewModel内部使用:

    LiveData<Data> data = 
    LiveDataTransformations.switchMap(searchStringLiveData, string ->  
    repo.loadData(string)))

将上述实时数据返回给活动,以便它可以观察和更新视图。

1
抱歉,我不太理解,请您能否澄清一下。我已经阅读了一些相关资料,但是从您的回答中无法确定我应该放置这段代码在哪里。例如,因为我正在尝试实现一个搜索功能,所以我是否还需要创建一个更新值的方法呢?比如,在我的ViewModel类的顶部添加可变LiveData私有MutableLiveData<String>名称;添加一个更新值的方法void setUserName(String userName) { this.name.setValue(userName); },然后我应该将switch map放在哪里? - martinseal1987
1
是的,正确的做法是使用数据绑定来直接更新数据。 - Rohit
从我尝试过的情况来看,它还没有正常工作,但我相信你是正确的,因此我已经接受了你的答案,并会在适当的时候提出一个新问题。非常感谢。 - martinseal1987

11

我曾经遇到过相同的问题,并且通过使用

switchMap

MutableLiveData

解决了这个问题。我们只需要使用MutableLiveData来设置editText的当前值,在用户搜索时调用setValue(editText.getText())

 public class FavoriteViewModel extends ViewModel {
            public LiveData<PagedList<TeamObject>> teamAllList;
        public MutableLiveData<String> filterTextAll = new MutableLiveData<>();

        public void initAllTeams(TeamDao teamDao) {
            this.teamDao = teamDao;
            PagedList.Config config = (new PagedList.Config.Builder())
                    .setPageSize(10)
                    .build();

            teamAllList = Transformations.switchMap(filterTextAll, input -> {
                if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
                    return new LivePagedListBuilder<>(
                            teamDao.loadAllTeam(), config)
                            .build();
                } else {
                    System.out.println("CURRENTINPUT: " + input);
                    return new LivePagedListBuilder<>(
                            teamDao.loadAllTeamByName(input), config)
                            .build();
                }

            });

            }

    }

片段的活动

viewModel = ViewModelProviders.of(activity).get(FavoriteViewModel.class);
                        viewModel.initAllTeams(AppDatabase.getInstance(activity).teamDao());
                        FavoritePageListAdapter adapter = new FavoritePageListAdapter(activity);
                        viewModel.teamAllList.observe(
                                activity, pagedList -> {
                                    try {
                                        Log.e("Paging ", "PageAll" + pagedList.size());

                                        try {
                                            //to prevent animation recyclerview when change the list
                                            recycleFavourite.setItemAnimator(null);
                                            ((SimpleItemAnimator) Objects.requireNonNull(recycleFavourite.getItemAnimator())).setSupportsChangeAnimations(false);

                                        } catch (Exception e) {
                                        }

                                        adapter.submitList(pagedList);

                                    } catch (Exception e) {
                                    }
                                });
                        recycleFavourite.setAdapter(adapter);

//first time set an empty value to get all data
                        viewModel.filterTextAll.setValue("");



                edtSearchFavourite.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.filterTextAll.setValue("%" + editable.toString() + "%");


                    }
                });

房间Dao

@Dao
public interface TeamDao {


        @Query("SELECT * FROM teams order by orders")
        DataSource.Factory<Integer, TeamObject> loadAllTeam();


        @Query("SELECT * FROM teams where team_name LIKE  :name or LOWER(team_name_en) like LOWER(:name) order by orders")
        DataSource.Factory<Integer, TeamObject> loadAllTeamByName(String name);


    }

PageListAdapter

public class FavoritePageListAdapter extends PagedListAdapter<TeamObject, FavoritePageListAdapter.OrderHolder> {
    private static DiffUtil.ItemCallback<TeamObject> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<TeamObject>() {
                // TeamObject details may have changed if reloaded from the database,
                // but ID is fixed.
                @Override
                public boolean areItemsTheSame(TeamObject oldTeamObject, TeamObject newTeamObject) {
                    System.out.println("GGGGGGGGGGGOTHERE1: " + (oldTeamObject.getTeam_id() == newTeamObject.getTeam_id()));
                    return oldTeamObject.getTeam_id() == newTeamObject.getTeam_id();
                }

                @Override
                public boolean areContentsTheSame(TeamObject oldTeamObject,
                                                  @NonNull TeamObject newTeamObject) {
                    System.out.println("GGGGGGGGGGGOTHERE2: " + (oldTeamObject.equals(newTeamObject)));
                    return oldTeamObject.equals(newTeamObject);
                }
            };

    private Activity activity;

    public FavoritePageListAdapter() {
        super(DIFF_CALLBACK);
    }

    public FavoritePageListAdapter(Activity ac) {
        super(DIFF_CALLBACK);
        this.activity = ac;

    }

    @NonNull
    @Override
    public OrderHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_favourite, parent, false);
        return new FavoritePageListAdapter.OrderHolder(view);

    }

    @Override
    public void onBindViewHolder(@NonNull OrderHolder holder,
                                 int position) {
        System.out.println("GGGGGGGGGGGOTHERE!!!");

        if (position <= -1) {
            return;
        }
        TeamObject teamObject = getItem(position);


        try {
                holder.txvTeamRowFavourite.setText(teamObject.getTeam_name());


        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    public class OrderHolder extends RecyclerView.ViewHolder {

        private TextView txvTeamRowFavourite;


        OrderHolder(View itemView) {
            super(itemView);
            txvTeamRowFavourite = itemView.findViewById(R.id.txv_team_row_favourite);
        }

    }
}

这个解决方案提供了很好的见解,谢谢!只是需要注意一下,Lambda函数有点棘手。teamAllList = Transformations.switchMap(filterTextAll, input -> { - Theophilus Abiola Alamu

6

以下是Kotlin的工作示例

在Fragment中:

binding.search.addTextChangedListener { text ->
            viewModel.searchNameChanged(text.toString())
        }


        viewModel.customers.observe(this, Observer {
            adapter.submitList(it)
            binding.swipe.isRefreshing=false
        })
  • 搜索 -> 是我的编辑文本
  • 客户 -> 是viewModel中的数据列表

视图模型

     private val _searchStringLiveData = MutableLiveData<String>()

         val customers = Transformations.switchMap(_searchStringLiveData){string->
                repository.getCustomerByName(string)
            }

    init {
            refreshCustomers()
            _searchStringLiveData.value=""
        }


fun searchNameChanged(name:String){
        _searchStringLiveData.value=name
    }

3

我遇到了相同的问题,并使用@Rohit的答案解决了它,谢谢!我将我的解决方案简化了一下以更好地说明。有类别,每个类别都有许多项目。LiveData只应返回一个类别的项。用户可以更改类别,然后调用fun search(id: Int),它会更改名为currentCategory的MutableLiveData的。然后这会触发switchMap并导致查询该类别的新项目:

class YourViewModel: ViewModel() {

    // stores the current Category
    val currentCategory: MutableLiveData<Category> = MutableLiveData()

    // the magic happens here, every time the value of the currentCategory changes, getItemByCategoryID is called as well and returns a LiveData<Item>
    val items: LiveData<List<Item>> = Transformations.switchMap(currentCategory) { category ->
           // queries the database for a new list of items of the new category wrapped into a LiveData<Item>
           itemDao.getItemByCategoryID(category.id)
    }

    init {
        currentCategory.value = getStartCategoryFromSomewhere()
    }

    fun search(id: Int) { // is called by the fragment when you want to change the category. This can also be a search String...
        currentCategory.value?.let { current ->
            // sets a Category as the new value of the MutableLiveData
            current.value = getNewCategoryByIdFromSomeWhereElse(id)
        }
    }
}

0
我使用以下方法实现了条形码搜索产品。
每当产品条形码的值发生变化时,将在房间数据库中搜索该产品。
@AppScoped
class PosMainViewModel @Inject constructor(
var localProductRepository: LocalProductRepository) : ViewModel() {

val productBarCode: MutableLiveData<String> = MutableLiveData()

val product: LiveData<LocalProduct> = Transformations.switchMap(productBarCode) { barcode ->
    localProductRepository.getProductByBarCode(barcode)
}

init {
    productBarCode.value = ""
}

fun search(barcode: String) {
    productBarCode.value = barcode
}}

在活动中

posViewModel.product.observe(this, Observer {
        if (it == null) {
           // not found
        } else {
            productList.add(it)
            rvProductList.adapter!!.notifyDataSetChanged()
        }
    })

用于搜索

posViewModel.search(barcode) //search param or barcode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接