Reputation: 5618
Little problem with my Android application and I don't know how to solve it with MVVM Cross.
Here is my ViewModel:
public class AddressesShowViewModel : MvxViewModel
{
public List<Address> Addresses { get; set; }
public AddressesShowViewModel(string addressesForListView)
{
Addresses = JsonConvert.DeserializeObject<List<Address>>(addressesForListView);
}
public IMvxCommand ShowItemCommand
{
get
{
//return new MvxRelayCommand<Type>((type) => this.RequestNavigate(type));
return new MvxRelayCommand(DoShowContact);
}
}
private Address selectedItem;
public Address SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; FirePropertyChanged(() => SelectedItem); }
}
private void DoShowContact()
{
RequestNavigate<AddressShowViewModel>();
}
}
My AddressesShow.axml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/INMobileCRM4Android.INMobileCRM4Android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'Addresses'},'ItemClick':{'Path':'ShowItemCommand'}, 'SelectedItem':{'Path':'SelectedItem'}}"
local:MvxItemTemplate="@layout/addresslistitem"
/>
</FrameLayout>
I would like to know, how I can get the SelectedItem from the ListView in AddressesShow.axml
.. I tried to create a Property 'SelectedItem'.. But its getting called at the beginning, when the ViewModel is created (and is obviously returning null), not when the Item is clicked.. Its btw a type of Address, not just a String or something.. Maybe any suggestions?
Upvotes: 4
Views: 3021
Reputation: 66882
The lack of SelectedItem
in Droid was identified as an issue last week during preparation for Daniel's talk at Build.
To workaround it, there were a couple of quick answers:
1 There is SelectedItemPosition
you can use for binding - this is an int
2 You can use a Click
ICommand/IMvxCommand
binding instead of using SelectedItem
- in your example, this would be the same axml but
public IMvxCommand ShowItemCommand
{
get
{
return new MvxRelayCommand<Address>(address => DoShowContact(address));
}
}
To be clear this Click
option above is what I would use.
If SelectedItem really is needed...
Then for a complete answer, Daniel and I prototyped a new binding. This binding was registered using:
registry.RegisterFactory(new MvxCustomBindingFactory<MvxBindableListView>("SelectedItem", adapterView => new MvxAdapterViewSelectedItemTargetBinding(adapterView)));
and contained the logic:
using System;
using Android.Widget;
using Cirrious.MvvmCross.Binding.Droid.Views;
using Cirrious.MvvmCross.Binding.Interfaces;
using Cirrious.MvvmCross.Interfaces.Platform.Diagnostics;
namespace Cirrious.MvvmCross.Binding.Droid.Target
{
#warning This needs to be redone for all adapterviews not just list view!
#warning The use of ItemClick instead of ItemSelected needs to be reinvestigated here!
public class MvxAdapterViewSelectedItemTargetBinding : MvxBaseAndroidTargetBinding
{
private readonly MvxBindableListView _view;
private object _currentValue;
public MvxAdapterViewSelectedItemTargetBinding(MvxBindableListView view)
{
_view = view;
((ListView)_view).ItemClick += OnItemClick;
}
private void OnItemClick(object sender, AdapterView.ItemClickEventArgs itemClickEventArgs)
{
var container = (_view.GetItemAtPosition(itemClickEventArgs.Position) as MvxJavaContainer);
if (container == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Missing MvxJavaContainer in MvxAdapterViewSelectedItemTargetBinding");
return;
}
var newValue = container.Object;
if (!newValue.Equals(_currentValue))
{
_currentValue = newValue;
FireValueChanged(newValue);
}
}
public override void SetValue(object value)
{
#warning Sort out Equals test here
if (value != null && value != _currentValue)
{
var index = _view.Adapter.GetPosition(value);
if (index < 0)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Value not found for spinner {0}", value.ToString());
return;
}
_currentValue = value;
_view.SetSelection(index);
}
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
public override Type TargetType
{
get { return typeof(object); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
((ListView)_view).ItemClick -= OnItemClick;
}
base.Dispose(isDisposing);
}
}
}
To test this worked, I used the Tutorial PullToRefresh code adapted using:
<Mvx.MvxBindableListView android:id="@android:id/list" android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'Emails'},'ItemClick':{'Path':'ShowItemCommand'},'SelectedItem':{'Path':'TheSelectedEmail'}}"
local:MvxItemTemplate="@layout/listitem_email"
/>
and:
public class SimpleEmail
{
public string From { get; set; }
public string Header { get; set; }
public string Message { get; set; }
}
private ObservableCollection<SimpleEmail> _emails;
public ObservableCollection<SimpleEmail> Emails
{
get { return _emails; }
private set { _emails = value; RaisePropertyChanged(() => Emails); }
}
private SimpleEmail _email;
public SimpleEmail TheSelectedEmail
{
get { return _email; }
set
{
_email = value;
MvxTrace.Trace(MvxTraceLevel.Error, "HELLO {0} ", value == null ? "null" : value.From);
}
}
One thing to be careful about in all this work is that a listview selected item in Android is slightly different to a listbox selected item in Silverlight/wp - e.g. it can be quite hard to get a listview in android to highlight the current selection and it can be quite hard to get the listview to generate selection changed events.
Note: I've logged an issue on Droid SelectedItem to https://github.com/slodge/MvvmCross/issues/52 - I'll make sure the binding is added to the core library in the near future
Upvotes: 3