Filter ListView with SearchView xamarin - xamarin.android

I want to filter Listview by Searchview
I use the following Adapter for the filter and it works if I haven't made any new additions to the adapter
When I add a new item to Listview, the search stops completely until I restart the program after adding, modifying or deleting it
full code
adapter class

Do you want to achieve the result like following GIF?
If you want to add the item to the listview, based on your adapter, you should item in the adapter like following code.
public class TableItemAdapter : BaseAdapter<TableItem>, IFilterable
{
public List<TableItem> _originalData;
public List<TableItem> _items;
private readonly Activity _context;
public TableItemAdapter(Activity activity, IEnumerable<TableItem> tableitems)
{
_items = tableitems.ToList();
_context = activity;
Filter = new TableItemFilter(this);
}
//Add data to the `_items`, listview will be updated, if add data in the activity,
//there are two different lists, so listview will not update.
public void AddData(TableItem tableItem)
{
_items.Add(tableItem);
NotifyDataSetChanged();
}
public override TableItem this[int position]
{
get { return _items[position]; }
}
public Filter Filter { get; private set; }
public override int Count
{
get { return _items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = _items[position];
View view = convertView;
if (view == null) // no view to re-use, create new
view = convertView ?? _context.LayoutInflater.Inflate(Resource.Layout.TableItem, null);
//view = _context.LayoutInflater.Inflate(Resource.Layout.TableItem, null);
view.FindViewById<TextView>(Resource.Id.Text1).Text = item.Heading;
view.FindViewById<TextView>(Resource.Id.Text2).Text = item.SubHeading;
return view;
}
public override void NotifyDataSetChanged()
{
// this.NotifyDataSetChanged();
base.NotifyDataSetChanged();
}
}
public class TableItemFilter :Filter
{
private readonly TableItemAdapter _adapter;
public TableItemFilter(TableItemAdapter adapter)
{
_adapter = adapter;
}
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
var returnObj = new FilterResults();
var results = new List<TableItem>();
if (_adapter._originalData == null)
_adapter._originalData = _adapter._items;
if (constraint == null) return returnObj;
if (_adapter._originalData != null && _adapter._originalData.Any())
{
results.AddRange(
_adapter._originalData.Where(
item => item.SubHeading.ToLower().Contains(constraint.ToString()) | item.Heading.ToLower().Contains(constraint.ToString())));
}
returnObj.Values = FromArray(results.Select(r => r.ToJavaObject()).ToArray());
returnObj.Count = results.Count;
constraint.Dispose();
return returnObj;
}
protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
using (var values = results.Values)
_adapter._items = values.ToArray<Java.Lang.Object>().Select(r => r.ToNetObject<TableItem>()).ToList();
_adapter.NotifyDataSetChanged();
// Don't do this and see GREF counts rising
constraint.Dispose();
results.Dispose();
}
}
public class JavaHolder : Java.Lang.Object
{
public readonly object Instance;
public JavaHolder(object instance)
{
Instance = instance;
}
}
public static class ObjectExtensions
{
public static TObject ToNetObject<TObject>(this Java.Lang.Object value)
{
if (value == null)
return default(TObject);
if (!(value is JavaHolder))
throw new InvalidOperationException("Unable to convert to .NET object. Only Java.Lang.Object created with .ToJavaObject() can be converted.");
TObject returnVal;
try { returnVal = (TObject)((JavaHolder)value).Instance; }
finally { value.Dispose(); }
return returnVal;
}
public static Java.Lang.Object ToJavaObject<TObject>(this TObject value)
{
if (Equals(value, default(TObject)) && !typeof(TObject).IsValueType)
return null;
var holder = new JavaHolder(value);
return holder;
}
}
}
Then in the activity, you add the data by adapter.
private void Button1_Click(object sender, System.EventArgs e)
{
tableItemAdapter.AddData(new TableItem() { Heading = "test1222", SubHeading = "sub Test" });
}
Here is my demo, you can download it.
https://github.com/851265601/Xamarin.Android_ListviewSelect/blob/master/XAListViewSearchDemo.zip

Related

Update custom listviewAdapter

I wanne add new data to my listviewAdapter, i tried it with clearing all the data before it and notifying that there is now data but it doesn't seem to work. I followed the Solution (stackoverflow) but it doesn't work.
Does anyone have an idea why?
public class ReportsListViewAdapter : BaseAdapter<IMobileReport>, IFilterable
{
internal List<IMobileReport> originalData;
internal List<IMobileReport> reports;
private Context context;
public override IMobileReport this[int position] => reports[position];
public ReportsListViewAdapter(Context context, IEnumerable<IMobileReport> reports)
{
this.reports = reports.OrderBy(report => report.StudyDate).ToList();
this.context = context;
Filter = new ReportsFilter(this);
}
public override int Count => this.reports.Count;
public Filter Filter { get; private set; }
public override long GetItemId(int position)
{
return position;
}
public void updateReportsList(List<MobileReport> newlist)
{
reports.AddRange(newlist);
this.NotifyDataSetChanged();
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
if(row == null)
{
row = LayoutInflater.From(context).Inflate(Resource.Layout.listView_reports_row, null, false);
}
var txtName = row.FindViewById<TextView>(Resource.Id.txtName);
txtName.Text = reports[position].Student.Name;
var txtFirstName = row.FindViewById<TextView>(Resource.Id.txtFirstName);
txtFirstName.Text = reports[position].Student.FirstName;
var txtSource = row.FindViewById<TextView>(Resource.Id.txtSource);
txtSource.Text = reports[position].Source;
var txtCritical = row.FindViewById<TextView>(Resource.Id.txtCritical);
txtSource.Text = reports[position].Critical.ToString();
return row;
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
reports = new List<IMobileReport>();
//Init();
_reportsHubConnector = new ReportsHubConnector();
#pragma warning disable 4014 // We cannot await this task here because the signature of the inherited method is void
Task.Factory.StartNew(async () =>
{
await _reportsHubConnector.StartConnection();
await _reportsHubConnector.SendT();
}, TaskCreationOptions.PreferFairness);
#pragma warning restore 4014
Console.WriteLine("HomeActivity: OnCreate");
SetContentView(Resource.Layout.activity_reports);
SupportActionBar.SetDisplayShowTitleEnabled(false);
SupportActionBar.SetDisplayHomeAsUpEnabled(false);
SupportActionBar.SetDisplayShowHomeEnabled(true);
WireUpElements();
listView = FindViewById<ListView>(Resource.Id.reports);
ReportsListViewAdapter adapter = new ReportsListViewAdapter(this, reports);
listView.Adapter = adapter;
searchView = FindViewById<SearchView>(Resource.Id.searchView1);
searchView.QueryTextChange += this.Filter;
listView.ItemClick += ItemClicked;
criticalButton = FindViewById<LinearLayout>(Resource.Id.AuthenticatorButton);
criticalButton.Click += criticalClicked;
_reportsHubConnector.ReportsRecieved += (reports) =>
{
adapter.updateReportsList(reports);
};
}
When i debug slowly the GetView does get triggered, maybe this is a clue to why its not being called when i don't debug or go over the code quick.
This.RunOnUiThread is never called but update is.
_reportsHubConnector.ReportsRecieved += (tmpReports) =>
{
adapter.updateReportsList(tmpReports);
this.RunOnUiThread(() =>
{
criticalButton.SetBackgroundColor(Android.Graphics.Color.Red);
});
};
i tried it with clearing all the data before it and notifying that there is now data but it doesn't seem to work.
From shared code , not seeing clear method , you can add reports.Clear() to check whether it works .
public void updateReportsList(List<MobileReport> newlist)
{
reports.Clear();
reports.AddRange(newlist);
this.NotifyDataSetChanged();
}
If not works , need to check whehter added newlist is the correct data format.
========================Update========================
In OnCreate method , where _reportsHubConnector.ReportsRecieved call update method modify as follow :
_reportsHubConnector.ReportsRecieved += (tmpReports) =>
{
adapter.updateReportsList(tmpReports);
};
Change reports argument to tmpReports to avoid mixing with the original data reports .
Therefore , there is another common way to update data of adapter as follow :
_reportsHubConnector.ReportsRecieved += (reports) =>
{
List<IMobileReport> tmp = new List<IMobileReport>();
tmp = reports ; // use a tmp list data for updating , not using original list data
adapter.updateReportsList(tmp);
};
============================Update===============================
From my sample project , I find a interesting phenomenon and that maybe the problem.
Here I will share my custom adapter HomeScreenAdapter :
public class HomeScreenAdapter : BaseAdapter<TableItem> {
List<TableItem> items;
Activity context;
public HomeScreenAdapter(Activity context, List<TableItem> items)
: base()
{
this.context = context;
this.items = new List<TableItem>();
this.items.AddRange(items);
//this.items = items;
}
public override long GetItemId(int position)
{
return position;
}
public override TableItem this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
View view = convertView;
if (view == null) // no view to re-use, create new
view = context.LayoutInflater.Inflate(Resource.Layout.CustomView, null);
view.FindViewById<TextView>(Resource.Id.Text1).Text = item.Heading;
view.FindViewById<TextView>(Resource.Id.Text2).Text = item.SubHeading+" items";
view.FindViewById<ImageView>(Resource.Id.Image).SetImageResource(item.ImageResourceId);
return view;
}
public void UpdateListView(List<TableItem> newTableItem)
{
items.Clear();
items.AddRange(newTableItem);
NotifyDataSetChanged();
}
}
You will see in the Constructor of HomeScreenAdapter , I commented this line of code this.items = items; . The reault that it will work:
However , if I use this.items = items; to replace this.items.AddRange(items);, it will update nothing even can not show anything in ListView .
public HomeScreenAdapter(Activity context, List<TableItem> items)
: base()
{
this.context = context;
this.items = new List<TableItem>();
//this.items.AddRange(items);
this.items = items;
}
The effect :
The possible reason :
If the equal sign is used here, the pointer address of the items will change. When the data is updated, it cannot point to the original data source, so the update cannot be successful.
Therefore , here Constructor of your code can modify as follow to check whehter it works :
public ReportsListViewAdapter(Context context, IEnumerable<IMobileReport> reports)
{
//this.reports = reports.OrderBy(report => report.StudyDate).ToList();
this.reports = new List<IMobileReport>();
this.reports.AddRange(reports.OrderBy(report => report.StudyDate).ToList());
this.context = context;
Filter = new ReportsFilter(this);
}
Related : Here is the sample project link .

Adding Searchview in Listview

I made an array to transfer the required data consisting of 3 elements(Heading,SubHeading, int ImageResourceId).
The data shows up well in listview but I couldn't do the research inside listview.
I want to do the research using searchview on specific SubHeading or both (Heading,SubHeading)
//on creat
listView.Adapter = new HomeScreenAdapter(this, HomeScreenAdapter.tableItems);
search.QueryTextChange += SearchV_QueryTextChange;
//filter listview
private void SearchV_QueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
{
}
//
// 3-class listview item id
public class TableItem
{
public string Heading;
public string SubHeading;
public int ImageResourceId;
public TableItem(string Heading, string SubHeading, int ImageResourceId)
{
this.Heading = Heading;
this.SubHeading = SubHeading;
this.ImageResourceId = ImageResourceId;
}
}
}
//
//class adapter & array item
class HomeScreenAdapter : BaseAdapter<TableItem>
{
public static List<TableItem> tableItems = new List<TableItem>();
List<TableItem> items;
Activity context;
public void filldata()
{
tableItems.Add(new TableItem("hussein", "devepoer", Resource.Drawable.imaga));
tableItems.Add(new TableItem("ahmed", "admin", Resource.Drawable.imagb));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.imagc));
tableItems.Add(new TableItem("ahmed", "admin", Resource.Drawable.imagd));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.image));
tableItems.Add(new TableItem("jasim", " manager", Resource.Drawable.imagf));
}
}
According to your code, I do one sample by SubHeading to filter ListView data.
Firstly, create TableItem class:
public class TableItem
{
public string Heading { get; set; }
public string SubHeading { get; set; }
public int ImageResourceId { get; set; }
}
Then creating UI for ListView item:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ImageView android:id="#+id/imageView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
And create Adapter and filter for data:
public class TableItemAdapter : BaseAdapter<TableItem>, IFilterable
{
private List<TableItem> _originalData;
private List<TableItem> _items;
private readonly Activity _context;
public TableItemAdapter(Activity activity, IEnumerable<TableItem> tableitems)
{
_items = tableitems.OrderBy(s => s.SubHeading).ToList();
_context = activity;
Filter = new TableItemFilter(this);
}
public override TableItem this[int position]
{
get { return _items[position]; }
}
public Filter Filter { get; private set; }
public override int Count
{
get { return _items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var view = convertView ?? _context.LayoutInflater.Inflate(Resource.Layout.TableItem, null);
var tableitem = _items[position];
var HeadingView = view.FindViewById<TextView>(Resource.Id.textView1);
var SubHeadingView = view.FindViewById<TextView>(Resource.Id.textView2);
var imageView = view.FindViewById<ImageView>(Resource.Id.imageView1);
HeadingView.Text = tableitem.Heading;
SubHeadingView.Text = tableitem.SubHeading;
imageView.SetImageResource(tableitem.ImageResourceId);
return view;
}
public override void NotifyDataSetChanged()
{
// If you are using cool stuff like sections
// remember to update the indices here!
base.NotifyDataSetChanged();
}
private class TableItemFilter : Filter
{
private readonly TableItemAdapter _adapter;
public TableItemFilter(TableItemAdapter adapter)
{
_adapter = adapter;
}
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
var returnObj = new FilterResults();
var results = new List<TableItem>();
if (_adapter._originalData == null)
_adapter._originalData = _adapter._items;
if (constraint == null) return returnObj;
if (_adapter._originalData != null && _adapter._originalData.Any())
{
// Compare constraint to all names lowercased.
// It they are contained they are added to results.
results.AddRange(
_adapter._originalData.Where(
item => item.SubHeading.ToLower().Contains(constraint.ToString())));
}
// Nasty piece of .NET to Java wrapping, be careful with this!
returnObj.Values = FromArray(results.Select(r => r.ToJavaObject()).ToArray());
returnObj.Count = results.Count;
constraint.Dispose();
return returnObj;
}
protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
using (var values = results.Values)
_adapter._items = values.ToArray<Java.Lang.Object>().Select(r =>r.ToNetObject<TableItem>()).ToList();
_adapter.NotifyDataSetChanged();
// Don't do this and see GREF counts rising
constraint.Dispose();
results.Dispose();
}
}
}
ObjectExtensions to implement filter:
public class JavaHolder : Java.Lang.Object
{
public readonly object Instance;
public JavaHolder(object instance)
{
Instance = instance;
}
}
public static class ObjectExtensions
{
public static TObject ToNetObject<TObject>(this Java.Lang.Object value)
{
if (value == null)
return default(TObject);
if (!(value is JavaHolder))
throw new InvalidOperationException("Unable to convert to .NET object. Only Java.Lang.Object created with .ToJavaObject() can be converted.");
TObject returnVal;
try { returnVal = (TObject)((JavaHolder)value).Instance; }
finally { value.Dispose(); }
return returnVal;
}
public static Java.Lang.Object ToJavaObject<TObject>(this TObject value)
{
if (Equals(value, default(TObject)) && !typeof(TObject).IsValueType)
return null;
var holder = new JavaHolder(value);
return holder;
}
}
Mainactivity.cs:
public class MainActivity : AppCompatActivity
{
private SearchView searchview1;
private ListView listview1;
private TableItemAdapter tableitemadapter;
private List<TableItem> tableitems;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
searchview1 = FindViewById<SearchView>(Resource.Id.searchView1);
listview1 = FindViewById<ListView>(Resource.Id.listView1);
tableitems = new List<TableItem>()
{
new TableItem(){Heading= "hussein", SubHeading="devepoer",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "ahmed", SubHeading="admin",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "jasim", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "cherry", SubHeading="admin",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "barry", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 },
new TableItem(){Heading= "annine", SubHeading="manager",ImageResourceId= Resource.Drawable.plu3 }
};
tableitemadapter = new TableItemAdapter(this,tableitems);
listview1.Adapter = tableitemadapter;
searchview1.QueryTextChange += Searchview1_QueryTextChange;
}
private void Searchview1_QueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
{
tableitemadapter.Filter.InvokeFilter(e.NewText);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
The sample in github:
https://github.com/CherryBu/Searchviewsample
The screenshot:

Updating adapter of AutoCompleteTextView from LiveData

I have a AutoCompleteTextView that I give it 2 different adapters depending on the amount of text that is being present at the textview - if it has 0 characters I want it to display a list of "recently searched" strings adapter, while if it has more than 1 characters I want it to display auto completion list.
My getRecentlySearchedQueries method along with the RecentSearchedViewModel-
private List<String> recentlySearchedQueries = new ArrayList<>(); // pasted from the top of the class
#Override
public void getRecentlySearchedQueries() {
recentSearchViewModel.getAllQueries().observe(getActivity(), databaseRecentlySearchList -> {
if (databaseRecentlySearchList == null) {
return;
}
for (int i = 0; i < databaseRecentlySearchList.size(); i++) {
Log.d("localDBValue", "Added value - " + databaseRecentlySearchList.get(i).toString() + "\n");
String query = databaseRecentlySearchList.get(i).getQuery();
recentlySearchedQueries.add(query);
}
//Log.d("localDBValue", "recent search list value - " + recentlySearchedQueries);
});
}
public class RecentSearchViewModel extends AndroidViewModel {
private RecentSearchRepository recentSearchRepository;
private LiveData<List<RecentSearchModel>> allRecentlySearched;
public RecentSearchViewModel(#NonNull Application application) {
super(application);
recentSearchRepository = new RecentSearchRepository(application);
allRecentlySearched = recentSearchRepository.getAllRecentSearches();
}
public void insert(RecentSearchModel model) {
recentSearchRepository.insert(model);
}
public void update(RecentSearchModel model) {
// add implementation in the future if needed
}
public void delete(RecentSearchModel model) {
// add implementation in the future if needed
}
public LiveData<List<RecentSearchModel>> getAllQueries() {
return allRecentlySearched;
}
}
public class RecentSearchRepository {
private RecentSearchDao recentSearchDao;
private LiveData<List<RecentSearchModel>> allRecentSearches;
public RecentSearchRepository(Application application) {
MarketplaceDatabase database = MarketplaceDatabase.getRecentSearchInstance(application);
recentSearchDao = database.recentSearchDao();
allRecentSearches = recentSearchDao.getRecentSearchList();
}
public void insert(RecentSearchModel model) {
new RecentSearchRepository.InsertRecentSearchAsyncTask(recentSearchDao).execute(model);
}
public void update (RecentSearchModel model) {
//TODO - implement in future if needed
}
public void delete(RecentSearchModel model) {
//TODO - implement in future if needed
}
public LiveData<List<RecentSearchModel>> getAllRecentSearches() {
return allRecentSearches;
}
private static class InsertRecentSearchAsyncTask extends AsyncTask<RecentSearchModel, Void, Void> {
private RecentSearchDao recentSearchDao;
public InsertRecentSearchAsyncTask(RecentSearchDao recentSearchDao) {
this.recentSearchDao = recentSearchDao;
}
#Override
protected Void doInBackground(RecentSearchModel... recentSearchModels) {
recentSearchDao.insert(recentSearchModels[0]);
return null;
}
}
private static class UpdateRecentSearchAsyncTask extends AsyncTask<RecentSearchModel, Void, Void> {
private RecentSearchDao recentSearchDao;
public UpdateRecentSearchAsyncTask(RecentSearchDao recentSearchDao) {
this.recentSearchDao = recentSearchDao;
}
#Override
protected Void doInBackground(RecentSearchModel... recentSearchModels) {
recentSearchDao.update(recentSearchModels[0]);
return null;
}
}
}
#Dao
public interface RecentSearchDao {
#Insert()
void insert(RecentSearchModel model);
#Update
void update(RecentSearchModel model);
#Delete
void delete(RecentSearchModel model);
#Query("select * from recent_search_table")
LiveData<List<RecentSearchModel>> getRecentSearchList();
}
#Entity(tableName = "recent_search_table")
public class RecentSearchModel {
#PrimaryKey(autoGenerate = true)
private int ID;
private String query;
public RecentSearchModel(){
}
public RecentSearchModel(String query) {
this.query = query;
}
public void setID(int ID) {
this.ID = ID;
}
public int getID() {
return ID;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
#Override
public String toString() {
return "RecentSearchModel{" +
"query='" + query + '\'' +
'}';
}
#Override
public boolean equals(#Nullable Object obj) {
if (obj instanceof RecentSearchModel)
return this.query.equalsIgnoreCase(((RecentSearchModel) obj).query);
return false;
}
}
So, what I am doing here is for start getting all values inside my local DB and adding them to my String list that is part of the adapter. So far so good.
The issue I am facing is that the adapter won't show the amount of strings available in the list that populates it. In fact, it sometimes shows a view half-cut with wierd information, sometimes does not show anything and sometimes shows part of the corrent information. What am I missing?
Another thing I am facing is that the "recently searched" adapter won't work when clicking on the AutoCompleteTextView - it only works when typing and deleting values so the char length is 0. How can I make it work from start of focus?
Here is the way I am populating the information to the ViewModel -
/**
* Shows the searched products following
*/
#Override
public void getSearchedProducts(String searchedQuery) {
MarketplaceUtils.getSearchedProducts(searchedQuery, marketApiCalls, false, initialSearchTake, initialMarketplacePage, new MarketplaceUtils.OnProductsFetchCompleteListener() {
#Override
public void onSuccess(List<MiniProductModel> list) {
if (!searchedQuery.equals(currentSearchedText))
return;
if (list == null) {
//reaching here means we do not have a result to show to the UI so we empty the list.
currentProductList.clear();
productsAdapter.notifyDataSetChanged();
return;
}
if (searchedQuery.length() > 3 && searchAutoCompleteStrings.contains(searchedQuery)) {
Log.d("localDBValue", "searchedValue - " + searchedQuery);
recentSearchViewModel.insert(new RecentSearchModel(searchedQuery));
}
mPresenter.setDiscoverProductsLayoutVisibility(View.GONE);
currentProductList.clear();
currentProductList.addAll(list);
productsAdapter.notifyDataSetChanged();
}
#Override
public void onError(Throwable throwable) {
Log.d("searchedProducts", throwable.getMessage());
}
});
}
The default behaviour for #Insert method of Room is OnConflictStrategy.ABORT - so what I did is to implement equals() method to verify that the RecentSearchModels that are being compared are compared by their string value. Still does seems to effect anything.

Custom MvxRecyclerAdapter

I am using MvvmCross in my Xamarin.Android application. I want to make my own custom MvxRecyclerAdapter so that I can have multiple buttons in each row of the MvxRecyclerView. Here is my custom MvxRecyclerView:
public class TwoPieceMvxRecyclerView : MvxRecyclerView
{
private bool _initialized;
public TwoPieceMvxRecyclerView(Context context, IAttributeSet attr) : base(context, attr)
{
}
public override Android.Support.V7.Widget.RecyclerView.Adapter GetAdapter()
{
if(!_initialized)
{
SetAdapter(new TwoPieceMvxRecyclerAdapter());
_initialized = true;
}
return base.GetAdapter();
}
}
And here is my custom MvxRecyclerAdapter:
public class TwoPieceMvxRecyclerAdapter : MvxRecyclerAdapter, IOnClickListener
{
private ICommand _itemClickPiece1;
private ICommand _itemClickPiece2;
private View _clickablePiece1;
private View _clickablePiece2;
public TwoPieceMvxRecyclerAdapter()
{
}
public ICommand ItemClickPiece1
{
get { return _itemClickPiece1; }
set
{
if (ReferenceEquals(_itemClickPiece1, value))
{
return;
}
_itemClickPiece1 = value;
}
}
public ICommand ItemClickPiece2
{
get { return _itemClickPiece2; }
set
{
if (ReferenceEquals(_itemClickPiece2, value))
{
return;
}
_itemClickPiece2 = value;
}
}
protected override Android.Views.View InflateViewForHolder(Android.Views.ViewGroup parent, int viewType, MvvmCross.Binding.Droid.BindingContext.IMvxAndroidBindingContext bindingContext)
{
var view = base.InflateViewForHolder(parent, viewType, bindingContext);
_clickablePiece1 = view.FindViewById<View>(Resource.Id.clickable_piece1);
_clickablePiece2 = view.FindViewById<View>(Resource.Id.clickable_piece2);
_clickablePiece1.SetOnClickListener(this);
_clickablePiece2.SetOnClickListener(this);
return view;
}
public void OnClick(View v)
{
if (v == _clickablePiece1)
{
ItemClickPiece1.Execute(null);
}
else if (v == _clickablePiece2)
{
ItemClickPiece2.Execute(null);
}
}
}
When I run the application I get this error:
Could not activate JNI Handle 0xbfd00978 (key_handle 0x6e44919) of
Java type
'md5bd77c484e80df14e69d8c5ab04394fe0/TwoPieceMvxRecyclerView' as
managed type
'AzzimovMobile.Droid.Components.TwoPieceMvxRecycler.TwoPieceMvxRecyclerView'.
System.InvalidOperationException: If you wan't to use single
item-template RecyclerView Adapter you can't change
it'sIMvxTemplateSelector to anything other than
MvxDefaultTemplateSelector
You are missing a constructor on your RecyclerView:
public TwoPieceMvxRecyclerView(IntPtr javaReference, JniHandleOwnership transfer): base(javaReference, transfer)
{
}
Also be aware you don't need to use a custom RecyclerView to change its Adapter. You can just grab the RecyclerView instance on your .cs view and set the adapter from there. Something like this should work:
public class MyView: MvxFragment<MyViewModel>
{
//...
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
// ...
var recycler = view.FindViewById<MvxRecyclerView>(Resource.Id.recycler);
recycler.Adapter = new TwoPieceMvxRecyclerAdapter(((IMvxAndroidBindingContext)BindingContext);
// you can even set a TemplateSelector here!
recycler.ItemTemplateSelector = new MyTemplateSelector();
// ...
return view;
}
}

Create a custom Spinner

I am trying to customize the MvxSpinner to add some additional controls, here is my code:
public class ChamSpinner : LinearLayout
{
public Spinner Spinner{ get; private set; }
public EventHandler<AdapterView.ItemSelectedEventArgs> ItemSelected;
public ChamSpinner (Context context, IAttributeSet attrs) : this (context, attrs, new ChamSpinnerAdapter (context))
{
}
public ChamSpinner (Context context, IAttributeSet attrs, IMvxAdapter adapter) : base (context, attrs)
{
((Activity)Context).LayoutInflater.Inflate (Resource.Layout.ChamSpinnerLayout, this);
Spinner = FindViewById<Spinner> (Resource.Id.ChamSpinnerSpinner);
int itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId (context, attrs);
int dropDownItemTemplateId = MvxAttributeHelpers.ReadDropDownListItemTemplateId (context, attrs);
adapter.ItemTemplateId = itemTemplateId;
adapter.DropDownItemTemplateId = dropDownItemTemplateId;
Adapter = adapter;
SetupHandleItemSelected ();
}
public new IMvxAdapter Adapter
{
get { return Spinner.Adapter as IMvxAdapter; }
set
{
var existing = Adapter;
if (existing == value)
return;
if (existing != null && value != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
Spinner.Adapter = value;
}
}
[MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get
{
return Adapter.ItemsSource;
}
set
{
Adapter.ItemsSource = value;
}
}
public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
}
public int DropDownItemTemplateId
{
get { return Adapter.DropDownItemTemplateId; }
set { Adapter.DropDownItemTemplateId = value; }
}
public ICommand HandleItemSelected { get; set; }
private void SetupHandleItemSelected ()
{
Spinner.ItemSelected += (sender, args) =>
{
var position = args.Position;
HandleSelected (position);
if (ItemSelected != null)
ItemSelected (sender, args);
};
}
protected virtual void HandleSelected (int position)
{
var item = Adapter.GetRawItem (position);
if (this.HandleItemSelected == null
|| item == null
|| !this.HandleItemSelected.CanExecute (item))
return;
this.HandleItemSelected.Execute (item);
}
}
And I am using it like this:
<cross.android.ChamSpinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxDropDownItemTemplate="#layout/myspinneritemdropdown"
local:MvxItemTemplate="#layout/myspinneritem"
local:MvxBind="ItemsSource MyItemsSource; SelectedItem MyItem; Mode TwoWay" />
The spinner is always empty, I tried to add a custom binding on ItemsSource property but the result stilll the same.
How can I do to show my items in my spinner?
Thank you in advance.
I think using BindingInflate instead of Inflate should fix it or even points you in the right direction. https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/BindingContext/IMvxAndroidBindingContext.cs
((IMvxBindingContextOwner)Context).BindingInflate(Resource.Layout.ChamSpinnerLayout, this);
I found this error in my log
MvxBind:Error: 32,12 View type not found - cross.android.ChamSpinner
My custom control is in another assembly so I added it to MvvmCross View assemblies is my Setup class like this
protected override IList<Assembly> AndroidViewAssemblies
{
get
{
var assemblies = base.AndroidViewAssemblies;
assemblies.Add(typeof(ChamSpinner).Assembly);
return assemblies;
}
}
Thank you Stuart for your advices and for your great Framework.

Resources