I\'m working on a project in android for a udacity course I\'m currently trying to implement a search function while adhering to android architecture components and using fi
I faced the same issue and I managed to fix it using
switchMap
and
MutableLiveData
We just need to use MutableLiveData to set the current value of editText, and when the user search we call 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();
}
});
}
}
in Activity of fragment
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() + "%");
}
});
Room 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);
}
}
}
in the Fragment
binding.search.addTextChangedListener { text ->
viewModel.searchNameChanged(text.toString())
}
viewModel.customers.observe(this, Observer {
adapter.submitList(it)
binding.swipe.isRefreshing=false
})
View Model
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
}
you can use Transformation.switchMap to do search operations.
In viewmodel create MutableLiveData which has latest search string.
Inside viewmodel use:
LiveData<Data> data =
LiveDataTransformations.switchMap(searchStringLiveData, string ->
repo.loadData(string)))
I faced the same issue and solved it with the answer of @Rohit, thanks! I simplified my solution a bit to illustrate it better. There are Categories
and each Category has many Items
. The LiveData should only return items from one Category. The user can change the Category and then the fun search(id: Int)
is called, which changes the value
of a MutableLiveData called currentCategory
. This then triggers the switchMap
and results in a new query for items of the category:
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)
}
}
}
I implement the bar code searching product using the following approach.
Everytime the value of productBarCode changes, the product will be searched in the room db.
@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
}}
In activity
posViewModel.product.observe(this, Observer {
if (it == null) {
// not found
} else {
productList.add(it)
rvProductList.adapter!!.notifyDataSetChanged()
}
})
for searching
posViewModel.search(barcode) //search param or barcode