I am new to Xamarin Android Development and I am using MvvmCross for binding data.I have SerachView on action-bar.I want to search data from list which is in ViewModel.How can I implement that ? I have searched for this issue on internet but all have used adapter and i want to search list-item without using adapter from ViewModel.I am not getting any idea how to do that.So anyone can suggest me an easy way?
Any suggestion or advice will be appreciated.
It is pretty simple.
Your SearchView is bound to a string property which you are using for filtering. Here I assume it is called SearchQuery.
It is not clear what criteria you want to use for filtering, I will assume that the ViewModel has a Name property, where the SearchQuery will be contained in that name.
So your ViewModel would look something like:
public class SearchViewModel : MvxViewModel
{
public string SearchQuery
{
get { return _searchQuery; }
set {
_searchQuery = value;
RaisePropertyChanged(() => SearchQuery);
RaisePropertyChanged(() => FilteredResults);
}
}
public List<ListItemViewModel> UnfilteredResults
{
get { return _unfilteredResults; }
set {
_unfilteredResults = value;
RaisePropertyChanged(() => UnfilteredResults);
RaisePropertyChanged(() => FilteredResults);
}
}
public List<ListItemViewModel> FilteredResults
{
get
{
if (string.IsNullOrEmpty(SearchQuery))
return UnfilteredResults;
return UnfilteredResults
.Where(r => r.Name.Contains(SearchQuery)).ToList();
}
}
}
So what happens is, whenever you enter a new value into the search box, it will trigger the PropertyChanged event on FilteredResults and use a simple LINQ query to filter the results.
If you don't want to swap out the entire list every time, you can do this with an ObservableCollection and add and remove items in that instead.
EDIT:
So as stated above you just bind the MvxListView to the new items source. Assuming you are using a AXML layout for your view:
<MvxListView
..
local:MvxBind="ItemsSource FilteredResults; ItemClick ResultClickedCommand" />
As for the SearchView, I just looked, there does not seem to be any code in MvvmCross to easily bind to that, and it does not inherit from EditText, so you need to do something like described here: https://stackoverflow.com/a/22501906/368379
Easiest way is probably simply to implement the SearchView.IOnQueryTextListener interface and in there set the new string you receive in the implementation on your ViewModel.
Related
What is the best way to do the following:
I have one json like:
{
"restaurants":[
{
"id":1,
"name":"Chinese Food",
"neighborhood":"New york",
"photograph":"abc.jpg",
"cuisine_type":"Chinese"
}, ..]
and second json response is
{ "menu":[
{
"restaurantId":1,
"allcategories":[
{
"id":"100",
"name":"Noodles",
"menu_items":[
{
"id":"800",
"name":"Hakka noodles",
"description":"Tasty hakka noodles",
"price":"350.00",
},
{
"id":"900",
"name":"Shezwan Hakka noodles",
"description":"Shezwan sauce spicy hakka noodles",
"price":"750.00",
}
]
}, ...],...}]}
I want to show all restaurants from first json response in recyclerview and along with it I want to show their respective menus also in the same card using the second response, where restaurant id is common in both the responses.
I have thought of couple of ways of doing it, like adding categoies and menu items in the first response/data class itself and then populating the UI from adapter or maybe using the common key in both the response as hashmap key and storing entire category from second response for each restaurant as value in hashmap and then in onbindViewholder finding category and then menu-items from second response object.
But this all seems little unclean, and i was thinking if there was a simpler way to achieve this using some kotlin operators. Can anyone suggest anything simpler and better?
P.S
I want to implement search filter also later, so I am looking for a solution in which i can search also between both the responses, like restaurant name from first response and menuitems from second response, and then my recyclerview will show the searched items.
Thanks in advance.
what I have done write now (using common key as Hashmap key) looks like this:
override fun onBindViewHolder(holder: RestaurantAdapter.RestViewHolder, position: Int) {
getCategoryList(restList.get(position).id)?.forEach {
it?.let {
holder.binding.tvMenuItem.text= "${it?.menuItems}"
}
}
}
fun getCategoryList(restId:Int?):ArrayList<Category?>?{
return menuMap.get(restId)?.categories
}
You can use
.filter { <filter predicate> } that returns a new list of items based on the condition you provided
map { } that that returns a list of items, where each item is transformed or modified from what you provided in the map function
You can find more Kotlin collection operations from the following links
https://github.com/benct/kotlin-cheat-sheet
https://medium.com/mobile-app-development-publication/kotlin-collection-functions-cheat-sheet-975371a96c4b
You can also perform a series of chained operations
Finally used Nuget Xamarin.Forms.DataGrid but know I am stuck with DataGrid.ItemSelected property.
I am using MVVM pattern with Commands, but DataGrid needs EventHandler for ItemSelected (I hope this is alternative for SelectionChangedCommand in CollectionView).
Is there way to use existing command as ItemSelected or if not how should I implement it using MVVM, main goal of command should be to update value of some IsSelected boolean value.
You can directly create binding on DataGrid.SelectedItem , and do something in Setter method .
//xaml
<dg:DataGrid SelectedItem="{Binding SelectedItem,Mode=TwoWay}"/>
//viewmodel
private object _SelectedItem;
public oibject SelectedItem
{
get
{
return SelectedItem;
}
set
{
_SelectedItem = value;
NotifyPropertyChanged();
//do something
}
}
Bottom Line Question
If I'm using MutableLiveData<List<Article>>, is there a way to properly notify observers when the title/content of an Article has changed, a new Article has been added, and an Article has been removed?
It seems the notifications are only possible when an entirely new collection is set on the LiveData, which would seem to result in a really inefficient UI refresh.
Hypothetical Example
Suppose the following...
My LiveData class looks something like this:
public class ArticleViewModel extends ViewModel {
private final MutableLiveData<List<Article>> mArticles = new MutableLiveData<>();
}
I want to display the Articles in a list by using the RecyclerView. So any time my Fragment observes a change in the ArticleViewModel's LiveData it calls the following method on my custom RecyclerView.Adapter class:
public class ArticleRecyclerViewAdapater extends RecyclerView.Adapter<Article> {
private final ArrayList<Article> mValues = new ArrayList<>();
public void resetValues(Collection<Article> articles) {
mValues.clear();
mValues.addAll(articles);
notifyDataSetChanged();
}
}
Finally, my application will allow the user to add a new Article, delete an existing Article, and change an existing Article's name (which needs to be updated in the RecyclerView list). How can I do that properly?
Add/Remove Article
It seems the LiveData construct doesn't notify observers if you add/remove an item from the underlying Collection. It seems you'd have to call LiveData#setValue, perhaps the ArticleViewModel would need a method that looks something like this:
public void deleteArticle(int index) {
final List<Article> articles = mArticles.getValue();
articles.remove(index);
mArticles.setValue(articles);
}
Isn't that really inefficient because it would trigger a complete refresh in the RecyclerView.Adapter as opposed to just adding/removing a single row?
Change Name
It seems the LiveData construct doesn't notify observers if you change the contents of an item in the underlying collection. So if I wanted to change the title of an existing Article and have that reflected in the RecyclerView then my ArticleViewModel would have to modify the object and call LiveData#setValue with the entire collection.
Again, isn't this really inefficient because it would trigger a complete refresh in the RecyclerView.Adapter?
Case1:
When you add or delete
So when you add or delete the element in the list you don't change the refernce of list item so every time you modify the liveData item you have to update live data value by calling setValue method(if you are updating the item on main thread)or Post value(when you are updating the value on background thread)
The problem is that it is not efficient
Solution
Use diffutil
Case 2:When you are updating the item property by editing the item.
The Problem
LiveData will only alert its observers of a change if the top level value has changed. In the case of a complex object, though, that means only when the object reference has changed, but not when some property of the object has changed.
Solution
To observe the change in property you need PropertyAwareMutableLiveData
class PropertyAwareMutableLiveData<T: BaseObservable>: MutableLiveData<T>() {
private val callback = object: Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
value = value
}
}
override fun setValue(value: T?) {
super.setValue(value)
value?.addOnPropertyChangedCallback(callback)
}
}
Two things to note here:
1.First is that our value is a generic type which implements the BaseObservable interface. This gives us access to the OnPropertyChangedCallback.
2.Next is that, whenever some property of the BaseObservable changes, we simply reset the top level value property to be its current value, thus alerting the observers of the LiveData that something has changed.
LiveData will only notify when its wrapped object reference is changed. When you assign a new List to a LiveData then it will notify because its wrapped object reference is changed but if add/remove items from a LiveData's List it will not notify because it still has the same List reference as wrapped object. So you can overcome this problem by making an extension of MutableLiveData as explained here in another stackoverflow question.
I know it's too late to answer.
But I hope it will help other developers searching for a resolution on a similar issue.
Take a look at LiveAdapter.
You just need to add the latest dependency in Gradle.
dependencies {
implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.4'
// kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects
}
and bind adapter with your RecyclerView
// Kotlin sample
LiveAdapter(
data = liveListOfItems,
lifecycleOwner = this#MainActivity,
variable = BR.item )
.map<Header, ItemHeaderBinding>(R.layout.item_header) {
onBind{
}
onClick{
}
areContentsTheSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
areItemSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
}
.map<Point, ItemPointBinding>(R.layout.item_point) {
onBind{
}
onClick{
}
areContentsTheSame { old: Point, new: Point ->
return#areContentsTheSame old.id == new.id
}
areItemSame { old: Header, new: Header ->
return#areContentsTheSame old.text == new.text
}
}
.into(recyclerview)
That's it. Not need to write extra code for adapter implementation, observe LiveData and notify the adapter.
I have a problem whereby upon every keypress from the user, the AutoCompleteTextView quickly hides and re-appears again (with an updated set of values).
Please suggest where my problem could be and whether you see any other problems with the below code.
Binding in the View:
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.Text)
.To(vm => vm.Email);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.PartialText)
.To(vm => vm.CurrentEmailEntry);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.ItemsSource)
.To(vm => vm.CurrentEmailAutoCompleteSuggestions);
AXML Layout:
<mvvmcross.droid.support.v7.appcompat.widget.MvxAppCompatAutoCompleteTextView
android:id="#+id/EmailAutoCompleteTextView"
android:layout_marginTop="#dimen/PaddingBetweenUserInputFields"
android:completionThreshold="1"
android:inputType="textEmailAddress" />
View Model Code:
private string _currentEmailEntry;
public string CurrentEmailEntry
{
get
{
return _currentEmailEntry;
}
set
{
_currentEmailEntry = value;
if (value == string.Empty)
{
_currentEmailEntry = null;
}
CurrentEmailAutoCompleteSuggestions = _emailAutoCompleteList
.Where(email => email.StartsWith(_currentEmailEntry, StringComparison.OrdinalIgnoreCase))
.ToArray();
RaisePropertyChanged(nameof(CurrentEmailEntry));
}
}
private static readonly string[] _emailAutoCompleteList = {"Gordon", "Gordy", "Go", "Freeman", "Is", "Alive"};
private IList<string> _currentEmailAutoCompleteSuggestions = _emailAutoCompleteList.ToList();
public IList<string> CurrentEmailAutoCompleteSuggestions
{
get { return _currentEmailAutoCompleteSuggestions; }
set
{
if (ReferenceEquals(_currentEmailAutoCompleteSuggestions, value))
return;
_currentEmailAutoCompleteSuggestions = value;
RaisePropertyChanged(nameof(CurrentEmailAutoCompleteSuggestions));
}
}
I use MvvmCross 4.0 (upgrade is not an option).
NB: I tried using an ObservableCollection instead of an IList and remove/add items to it (not re-assign the collection itself) but after that the setter for CurrentEmailEntry stopped receiving values after the user typed the first character into the text view. The code inside MvxFilteringAdapter seems to be stuck waiting on a reset event.
Since no one answered either here or on the MvvmCross Xamarin Slack channel I eventually discovered the solution myself.
It was the re-creation of the list bound to the ItemsSource that led to the strange behaviour in question.
The ObservableCollection with Clear()/Add() instead of re-creating was indeed the way to go. The stuck behaviour (waiting on the reset event) described in the last question paragraph was caused by the absence of proper thread dispatching (my application is multi-threaded).
As soon as I wrapped my observable collection with a proxy collection that always raised the CollectionChanged event on the UI thread, the problem disappeared.
Leaving this here for the benefit of future generations.
I have a RecyclerView with a FirebaseRecyclerAdapter. I want to populate the RecyclerView with a list of names when the user starts typing into the SearchView.
public class SchoolsAdapter extends FirebaseRecyclerAdapter<School, SchoolsAdapter.SchoolViewHolder> {
public SchoolsAdapter(Query ref) {
super(School.class, R.layout.item_school, SchoolViewHolder.class, ref);
}
#Override
public void populateViewHolder(SchoolViewHolder schoolViewHolder, School school, int position) {
schoolViewHolder.name.setText(school.getName());
schoolViewHolder.address.setText(school.getAddress());
}
static class SchoolViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public TextView address;
public SchoolViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.school_item_tview_name);
address = (TextView) itemView.findViewById(R.id.school_item_tview_address);
}
}
}
I'm guessing I need to add a QueryTextListener to the searchview that would update the Query in the adapter. Would this break the FirebaseRecyclerAdapter?
Or should I
#Override
public boolean onQueryTextChange(String newText) {
mRecyclerView.setAdapter(new SchoolAdapter(ref.orderByChild("name").startAt(userQuery).endAt(userQuery+"~"))
return false;
}
whenever the user types something?
Also the docs talk about ordering and sorting firebase queries but don't explicitly say the best way to do string pattern matching. What's the best way to do string matching so that the recycler view shows all results which have the search query as a substring of the database record, and possibly those that are 1 edit distance away as well.
Also a way to ignorecase on queries?
I just finished doing something near to what you're looking for, I'm not sure it's the most elegant solution, but I'll throw out some ideas and if you think my idea will help I can definitely provide some examples.
Firstly, when I extended the base FirebaseAdapter I added a new filter named mFullList, since mItems of the FirebaseAdapter will be used for the display list, I don't want to keep going back to the network when I didn't have to. I then override all the methods in my child class to update mFullList with the values from the Firebase callbacks, sort them, filter them then call super.X() with the new list.
Quickly:
public reset(List)
mFullList = List
Collections.sort(mFullList, Comparator)
getFilter().filter(filterString)
The filterString is a field within the Adapter and is updated during the call to getFilter().filter(). During the perform filter I then loop through the mFullList and do a compare of:
mFullList.get(pos).getName().toLowerCase().contains(filterString.toLowerCase);
Once fitlering is done you have a new list that is passed to Filter.publishResults in the FilterResults object. publishResults calls a method in the class that performs the update and notify.
filterCompleted(List)
getItems().clear
getItems().addAll
notify
Essentially, I didn't want the FirebaseAdapater to stop getting the full list of items, I just wanted the users request to filter that full list and handle their request appropriately. Also, I didn't see a point to the added network requests based the user typing an extra character.
Using this method you can just use:
adapter.getFilter().filter("something")
to filter the list based on your updated field, and
adapter.getFilter().filter("")
to reset the full list (as long as your performFilter() handled it correctly. This way new updates from FireBase will be filtered based on the users selection, as well as when a user types in new values, won't require making new Firebase network requests.