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 firestore and room I'm fairly new to all these concepts so please point out anything that seems wrong.
So I made a database repository to keep my firestore and room databases in sync and to deliver the data. I'm then using viewmodel and the observer pattern (I think) so my observer gets the data and looks for changes gives it to my adapter (refreshMyList(List)) which populates a recyclerview like this :
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);
}
}
});
I now want to perform a search of the data, I have my room queries all set up fine and I have methods in my data repository to get contacts based on a search string but I cant seem to refresh my list I've read that there are ways to do it like Transformations.switchMap ? but i cant seem to wrap my head around how it works can anyone help me
Currently I'm trying to return a List of results from an async task, it used to return live data but I changed it as getValue() was always null, not sure if that's correct, heres the async :
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;
}
}
I call this from my contacts repository in its own sort of wrapper :
public List<DatabaseContacts> getContactByName(String name) throws
ExecutionException, InterruptedException {
//return databaseContactsDao.findByName(name);
return new searchContactByName(databaseContactsDao).execute(name).get();
}
and this is called from my view model like this :
public List<DatabaseContacts> getContactByName(String name) throws
ExecutionException, InterruptedException {
return contactRepository.getContactByName(name);
}
I'm then calling this from my fragment :
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);
}
}
and this is called from an on search query text changed method in my onCreateOptionsMenu :
#Override
public boolean onQueryTextChange(String newText) {
try {
searchDatabase(newText);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
but it just does nothing my original recyclerview contents never change any ideas?
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)))
Return the above live data to activity so it can observe and update view.
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);
}
}
}
Here is a working example in KOTLIN
in the Fragment
binding.search.addTextChangedListener { text ->
viewModel.searchNameChanged(text.toString())
}
viewModel.customers.observe(this, Observer {
adapter.submitList(it)
binding.swipe.isRefreshing=false
})
search -> is my edit text
customers -> is the data list in the viewModel
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
}
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
Using two way data binding I'm trying to fit a double(Double) inside a EditText type field.
I have tried with converter functions (with #InverseMethod) and also tried to write a #BindingAdapter with #InverseBindingAdapter.
I think I may be missing something crucial cause:
"#={`` + muObject.myDecimal}"
reveals 'null' in the EditText field.
The #InverseFunction method crashes, and the binding adapter way did not work either...
Could someone please point me in the right direction?
Thanks
ViewModel code:
Note that the BaseObservableViewModel extends ViewModel from architecture components and contains the contents of the BaseObservable class (tip from Yigit Boyar).
Also note that none of the fields in the QualityControl class are observable.
At last: Note that the getter/setter for measurementKm are a test. measurementKm is Double field in the QualityControl class and I would prefer to bind directly to that field.
public final class QualityControlViewModel extends BaseObservableViewModel {
#SuppressWarnings("unused")
private static final String TAG = "QualityControlVM";
private QualityControl qualityControl;
private int position;
public String measurementKm = "";
QualityControlViewModel(Application application) {
super(application);
}
public QualityControl getQualityControl() {
return qualityControl;
}
public void setQualityControl(QualityControl qualityControl) {
this.qualityControl = qualityControl;
// Initialize massMeasurementPlaceSelected
if (isValidMassMeasurementPlace()) massMeasurementPlaceSelected = true;
setMeasurementKm(qualityControl.getMeasurementKm());
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
private Double getMeasurementKm() {
if (this.measurementKm.length() > 0) {
return Double.parseDouble(this.measurementKm);
} else {
return 0.0;
}
}
private void setMeasurementKm(Double measurementKm) {
if (qualityControl.getMeasurementKm() != null) {
this.measurementKm = String.valueOf(qualityControl.getMeasurementKm());
}
notifyChange();
}
}
I am learning Observer pattern, I want my observable to keep track of a certain variable when it changes it's value and do some operations, I've done something like :
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Observable<Integer> mObservable = Observable.just(VARIABLE_TO_OBSERVE);
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
But doMethod2() doesn't get called
Nothing is magic in the life : if you update a value, your Observable won't be notified. You have to do it by yourself. For example using a PublishSubject.
public class Test extends MyChildActivity {
private int VARIABLE_TO_OBSERVE = 0;
Subject<Integer> mObservable = PublishSubject.create();
protected void onCreate() {/*onCreate method*/
super();
setContentView();
method();
changeVariable();
}
public void changeVariable() {
VARIABLE_TO_OBSERVE = 1;
// notify the Observable that the value just change
mObservable.onNext(VARIABLE_TO_OBSERVE);
}
public void method() {
mObservable.map(value -> {
if (value == 1) doMethod2();
return String.valueOf(value);
}).subScribe(string -> System.out.println(string));
}
public void doMethod2() {/*Do additional operations*/}
}
If interested here a Kotlin version of Variable class, which lets subscribers to be updated after every variable change.
class Variable<T>(private val defaultValue: T) {
var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
val observable = BehaviorSubject.createDefault(value)
}
Usage:
val greeting = Variable("Hello!")
greeting.observable.subscribe { Log.i("RxKotlin", it) }
greeting.value = "Ciao!"
greeting.value = "Hola!"
This will print:
"Hello!"
"Ciao!"
"Hola!"
#dwursteisen Nothing is magic, no, but I think we can get it a little more magic than that... 😊
How about using an Rx BehaviourSubject in this way:
import rx.functions.Action1;
import rx.subjects.BehaviorSubject;
public class BehaviourSubjectExample {
public BehaviourSubjectExample() {
subject.skip(1).subscribe(new Action1<Integer>() {
#Override
public void call(Integer integer) {
System.out.println("The value changed to " + integer );
}
});
}
public final BehaviorSubject<Integer> subject = BehaviorSubject.create(0);
public int getValue() { return subject.getValue(); }
public void setValue(int value) { subject.onNext(value); }
}
Remove the .skip(1) if you want the observing code to see the initial value.
The variable backing remains with the BehaviourSubject and can be accessed through conventional Java Getter/Setter. This is a toy example of course: If your use case were really this simple there'd be no excuse for not just writing:
private int value = 0;
public int getValue() { return value; }
public void setValue(int value) {
this.value = value;
System.out.println("The value changed to " + value );
}
...but the use of BehaviourSubject lets you bridge changes to other Rx data-streams inside your class for composing more advanced behaviours.
public class CartItemViewModel : MvxNotifyPropertyChanged
{
public double SubTotal
{
get { return UnitPrice * Quantity; }
set
{
//RaisePropertyChanged("TotalValue")
}
}
}
public class CartViewModel : MvxViewModel
{
public double TotalValue
{
get
{
foreach (var item in cartlist)
{
totalvalue += item.UnitPrice;
}
return totalvalue;
}
set
{
TotalValue = value;
}
}
private double totalvalue;
}
I want to modify TotalValue property when SubTotal property is changed. Both are in different classes. How to make it possible?
It is not working when I pop up Raispropertychanged("TotalValue") in SubTotal.
Please Help!
You should handle the PropertyChanged event for all the instances of CartItemViewModel inside the CartViewModel.
You receive PropertyChangedEventArgs which has a member called PropertyName. When its value is "SubTotal" then you can call Raispropertychanged(TotalValue).
The code is completely added to CartViewModel.
Is it possible to bind android checkbox to execute a command on change? Could not find an example
Standard approach would be to simply bind to property of type bool in your viewmodel and perform your logic in setter of this property. Your binding will then look like this:
local:MvxBind="Checked IsChecked"
However if you really need bind to Command, you can also bind to Click event:
local:MvxBind="Checked IsChecked; Click YourCommand;"
ViewModel:
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
RaisePropertyChanged(() => IsChecked);
}
}
public ICommand YourCommand
{
get
{
return new MvxCommand(() =>
{
var isChecked = IsChecked;
//Now you can use isChecked variable
});
}
}
Note that you don't recieve value of the checkbox in your command parameter, so you need to bind to the bool property anyway. Another problem with this solution is that you must rely on a fact, that setter of your property would be called before your command.
If you really need to have command with bool parameter, then you can definitely do that. Awesome thing about MvvmCross framework is that you can always extend its functionality. In your case you would need to implement custom binding for CheckBox. Good starting point may be here: http://slodge.blogspot.cz/2013/06/n28-custom-bindings-n1-days-of-mvvmcross.html
Edit: To show how easy it is I gave it a try and implement simple command binding with bool parameter. (No CanExecute check). In case anyone is interested, here is the code.
Binding class:
public class CheckBoxChangedBinding
: MvxAndroidTargetBinding
{
private ICommand _command;
protected CheckBox View
{
get { return (CheckBox) Target; }
}
public CheckBoxChangedBinding(CheckBox view)
: base(view)
{
view.CheckedChange += CheckBoxOnCheckedChange;
}
private void CheckBoxOnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
if (_command == null)
return;
var checkBoxValue = e.IsChecked;
_command.Execute(checkBoxValue);
}
protected override void SetValueImpl(object target, object value)
{
_command = value as ICommand;
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override Type TargetType
{
get { return typeof (ICommand); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var view = View;
if (view != null)
{
view.CheckedChange -= CheckBoxOnCheckedChange;
}
}
base.Dispose(isDisposing);
}
}
In Setup.cs:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<CheckBox>("CheckedChanged",
checkBox => new CheckBoxChangedBinding(checkBox));
}
In your layout:
<CheckBox
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
local:MvxBind="CheckedChanged CheckBoxCheckedCommand" />
And finally ViewModel:
public ICommand CheckBoxCheckedCommand
{
get
{
return new MvxCommand<bool>(isChecked =>
{
var parameter = isChecked;
});
}
}