MvxExpandableListAdapter SetItemsSource is not called - xamarin.android

I have this code snippet to using MvxExpandableListView
public class ExpandView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.ExpandView);
MvxExpandableListView list = FindViewById<MvxExpandableListView>(Resource.Id.TheExpandView);
list.SetAdapter(new MyCustomAdaptor(this, (IMvxAndroidBindingContext)BindingContext));
}
private class MyCustomAdaptor : MvxExpandableListAdapter
{
private IList _itemsSource;
public MyCustomAdaptor(Context context, IMvxAndroidBindingContext bindingContext)
: base(context, bindingContext)
{
}
protected override void SetItemsSource(IEnumerable value)
{
Mvx.Trace("Setting itemssource");
if (_itemsSource == value)
return;
var existingObservable = _itemsSource as INotifyCollectionChanged;
if (existingObservable != null)
existingObservable.CollectionChanged -= OnItemsSourceCollectionChanged;
_itemsSource = value as IList;
var newObservable = _itemsSource as INotifyCollectionChanged;
if (newObservable != null)
newObservable.CollectionChanged += OnItemsSourceCollectionChanged;
if (value != null)
{
}
else
base.SetItemsSource(null);
}
}
}
When the view is loaded the override method SetItemsSource does not get called , but when I try to navigate away from the page SetItemsSource gets called with a null value.

You need to set the property ItemsSource.Looking at the source of MvxAdapter:
[MvxSetToNullAfterBinding]
public virtual IEnumerable ItemsSource
{
get { return _itemsSource; }
set { SetItemsSource(value); }
}
The setter of ItemsSource calls the method SetItemsSource.
MvxExpandableListAdapter inherits MvxAdapter.
Source:
https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/Binding/Droid/Views/MvxExpandableListAdapter.cs
https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/Binding/Droid/Views/MvxAdapter.cs

Related

Filter ListView with SearchView xamarin

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

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;
}
}

How to get the ActionDescription UniqueId from within OnResultExecuted

In ASP.NET MVC, does anyone know a trick to access the ActionDescriptor.UniqueId from within OnResultExecuted? I need to pass information from OnActionExecuting to OnResultExecuted in a way that will work if multiple actions are executed during the one HttpRequest.
For example:
private Dictionary<string,Foo> _foos
{
get { return HttpContext.Current.Items["foos"] as Dictionary<string,Foo>; }
set { HttpContext.Current.Items["foos"] = value; }
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var foos = _foos;
foos[context.ActionDescriptor.UniqueId] = new Foo();
_foos = foos;
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var actionUniqueId = ????
var foo = _foos[actionUniqueId]
}
You can custom the FilterAttributeFilterProvider and ActionFilterAttribute to implement it.
First you can create a filter that inherit the ActionFilterAttribute and contains ActionDescriptor property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public ActionDescriptor ActionDescriptor { get; set; }
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var actionUniqueId = ActionDescriptor.UniqueId;
//code..
}
}
Then you need create a filter provider inherit the FilterAttributeFilterProvider and override the GetFilters method:
public class MyFilterProvider : FilterAttributeFilterProvider
{
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (controllerContext.Controller != null)
{
foreach (FilterAttribute attr in GetControllerAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Controller, order: null);
}
foreach (FilterAttribute attr in GetActionAttributes(controllerContext, actionDescriptor))
{
var myAttr = attr as MyActionFilterAttribute;
if (myAttr != null)
{
myAttr.ActionDescriptor = actionDescriptor;
}
yield return new Filter(attr, FilterScope.Action, order: null);
}
}
}
}
You can see at the GetFilters method, We set the ActionDescriptor property if the filter type is MyActionFilterAttribute.
Finally, At Global.asax, you need use MyFilterProvider instance to replace the FilterAttributeFilterProvider instance in Providers collection:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//replace the FilterAttributeFilterProvider in providers collection
for (int i = 0; i < FilterProviders.Providers.Count; i++)
{
if (FilterProviders.Providers[i] is FilterAttributeFilterProvider)
{
FilterProviders.Providers[i] = new MyFilterProvider();
break;
}
}
//other global init code...
}
}

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.

Controller action called with old parameter

here in my company we can't instantiate a controller for each new request. We have to store it in the session and re-utilize it every time, i know this is wrong, but we have to keep the state of the controller between requests. So here's what we did:
We created this controller factory:
public class SGVControllerFactory : IControllerFactory
{
public IController CreateController(RequestContext requestContext, string controllerName)
{
string _SessionId = controllerName + "Controller";
foreach (var _Object in HttpContext.Current.Session)
{
if (_Object.ToString() == _SessionId)
{
IController _Controller = (IController)HttpContext.Current.Session[_Object.ToString()];
return _Controller;
}
}
return null;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
//We never release our controllers!!!
}
}
And we have this base controller:
public class SGVController : Controller
{
protected override void Execute(RequestContext requestContext)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null)
{
throw new ArgumentException("Http context is null", "requestContext");
}
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope())
{
ExecuteCore();
Dispose();
}
}
}
The only thing this controller class does differently from the default MVC controller is that it doesn't limit itself to be called just once.
Now, my problem is.. if I have this action:
public JsonResult Foo(string Bar) {
return Json(new List<string> { Bar, Bar });
}
The 'Bar' parameter will aways have the value of the first call to the action. I can't find anything that explains that. The request parameter dictionary has the right values, but the action still gets the old value.
You may try to reinit the ValueProvider and the TempData by overriding the Initialize method to have the new values being handled.
public class SGVController : Controller
{
protected override void Initialize(RequestContext requestContext)
{
this.TempData = null;
this.ValueProvider = null;
base.Initialize(requestContext);
}
protected override void Execute(RequestContext requestContext)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null)
{
throw new ArgumentException("Http context is null", "requestContext");
}
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope())
{
ExecuteCore();
Dispose();
}
}
}
Hope this will help,

Resources