How do I bind to Spinner.SelectedItem - xamarin.android

I would like to bind the selected text from a spinner to a string named SelectedRole in my ViewModel. This is what I did
this.Bind(ViewModel, vm => vm.SelectedRole, v => v.roleSpinner.SelectedItem.ToString());
However, I ran into an exception.
System.NotSupportedException: Index expressions are only supported with constants.

The SelectedItem property of Spinner has read-only access (no setter), so Bind won't work since it's two-way.
One alternative is to install the ReactiveUI.Events package and use the ItemSelected observable like this:
_spinner.Events().ItemSelected
.Select(_ => _spinner.SelectedItem.ToString())
.BindTo(ViewModel, x => x.Selected);
and of course the view model property is reactive:
private string _selected;
public string Selected
{
get => _selected;
set => this.RaiseAndSetIfChanged(ref _selected, value);
}
and if you want to initialize the spinner value, use the SetSelection method:
_spinner.SetSelection(2);
I tested all this on my phone and it works as expected. Let me know if this functionality fits your needs.

Related

Vaadin data Binder - ComboBox issues

Later Edit: I noticed that by returning one of the options in ValueProvider's apply method leads to having the check mark present, but appears to show the previous select too. I.e. if the current and previous values are distinct, two check marks are shown.
I am having troubles with ComboBox binding. I cannot get the com.vaadin.flow.data.binder.Binder properly select an option inside the combobox - i.e. tick the check mark in the dropdown.
My binder is a "generic", i.e. I am using it along with a Map, and I provide dynamic getters/setters for various map keys. So, consider Binder<Map>, while one of the properites inside the Map should be holding a Person's id.
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(new ItemLabelGenerator<Person>() {
#Override
public String apply(final Person p) {
return p.getName();
}
});
binder.bind(combobox, new ValueProvider<Map, Person>() {
#Override
public Person apply(final Map p) {
return new Person((Long)p.get("id"), (String)p.get("name"));
}
}, new Setter<Map, Person>() {
#Override
public void accept(final Map bean, final Person p) {
bean.put("name", p.getName());
}
});
Wondering what could I possibly do wrong...
Later edit: Adding a screenshot for the Status ComboBox which has a String for caption and Integer for value.
Your problem is that you are creating a new instance in your binding, which is not working. You probably have some other bean, (I say here Bean) where Person is a property. So you want to use Binder of type Bean, to bind ComboBox to the property, which is a Person. And then populate your form with the Bean by using e.g. binder.readBean(bean). Btw. using Java 8 syntax makes your code much less verbose.
Bean bean = fetchBean();
Binder<Bean> binder = new Binder();
ComboBox<Person> combobox = new ComboBox<>("Person");
List<Person> options = fetchPersons();
combobox.setItems(options);
combobox.setItemLabelGenerator(Person::getName);
binder.forField(combobox).bind(Bean::getPerson, Bean::setPerson);
binder.readBean(bean);

MVVMCross ListView - how to populate only with the list items matches certain conditions?

MVVMCross ListView - How to display a list of items in a listview grouped by a status? For exampled activate or deactivated
I have added two listview to the layout and set the datasource to have two lists (activatedItems and deactivatedItems => both of them derived from ListOfItems. ) using the xaml data binding with now code behind. But had a problem in the data not updated when there is a change to the underlying list (ListOfItems).
To resolve this, I have to re create the UI by setting the content view whenever there a change to the data set.
This is not an elegant solution and I would like to have one listview with sections "Activated" and "Deactivated". Then on touching the Activated item should get added to the Deactivated list and the UI should reflect the same.
Since you have a LoginItemModel with a Title property (let's assume it's a string property), I would recommend you to extract this to an ILoginItemModel interface and also add a bool IsHeader property. This way:
public interface ILoginItemModel {
public bool IsHeader { get; }
public LoginSection ItemGroup { get; }
public string Title { get; }
}
Make your LoginItemModel extend this ILoginItemModel interface and make IsHeader always return false.
public bool IsHeader => false;
Write a second class extending this same interface, let's call it LoginItemHeaderModel. This class will just have these three properties:
public class LoginItemHeaderModel : ILoginItemModel {
public bool IsHeader => true;
LoginSection _itemGroup;
public LoginSection ItemGroup => _itemGroup;
string _title;
public string Title => _title;
public LoginItemHeaderModel(LoginSection itemGroup, string title) {
_itemGroup = itemGroup;
_title = title;
}
}
Now we have two models that fits into a single IList<ILoginItemModel>.
Back to your view model, you can declare a fourth list property and put it all together along with your new headers:
public List<ILoginItemModel> SectionedLoginItems {
get {
var values = Enum.GetValues(typeof(LoginSection)).Cast<LoginSection>();
List<ILoginItemModel> list = new List<ILoginItemModel>();
foreach (LoginSection loginSection : values) {
list.Add(new LoginItemHeaderModel(loginSection, loginSection.ToString()));
list.AddRange(LoginItems.Where(l => l.ItemGroup == loginSection));
}
return list;
}
}
Now you have your single sorted and with section headers list.
You should now write a new layout for those header items (aren't you going to make them look like the common items, right?) and, in your custom MvxAdapter, inside the GetBindableView method, check whether the object source (which will be an ILoginItemModel object) is a header item or not.
Whenever you make a change to ActiveLoginItems or DeactivatedLoginItems, make a call to RaisePropertyChanged in your ViewModel i.e.
RaisePropertyChanged(() => ActiveLoginItems);
or
RaisePropertyChanged(() => DeactivatedLoginItems);
That should make the MvxListView update with the changes.

How to get SelectList to honor DisplayName annotation with enums?

I am using enums in my ViewModels to populate a DropDownListFor like this:
#Html.DropDownListFor(model => model.SelectedDropDownValue,
new SelectList(Enum.GetValues(typeof(SearchOptions)), SearchOptions.SSN))
This works well. However, I would like to display the DisplayName property of the DisplayNameAttribute that is associated with each value within the enum.
I have search Stackoverflow and seen a number of ways to do this with helpers, extensions, etc. Is it possible to, within a single statement like this, easily tell the SelectList to use the DisplayNameAttribute?
I'm thinking something like:
#Html.DropDownListFor(model => model.SelectedDropDownValue,
new SelectList(Enum.GetValues(typeof(SearchOptions),
"Value", GetDisplayAnnotation()), SearchOptions.SSN))
I have written some code that does the same thing except it looks for the DescriptionAttribute.
Modified for your situation it looks like this:
public static string GetDescription(this Enum value)
{
if (value == null)
throw new ArgumentNullException("value");
var attribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DisplayNameAttribute), false)
.Cast<DisplayNameAttribute>()
.FirstOrDefault();
return attribute == null ? value.ToString() : attribute.DisplayName;
}
Rather than create a select list and call the #Html.DropDownList in my view, I have created editor and display templates that look a bit like this (Called Enum.cshtml):
var values = Enum.GetValues(enumType)
.Cast<Enum>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.GetDescription(),
Value = v.ToString(),
});
Html.DropDownList("", values)
I then decorate any enum properties in my ViewModels with the UIHint attribute and any enum properties use the above code automatically.
public class MyViewModel
{
[UIHint("Enum")]
public MyEnumType MyProperty { get; set; }
}
There are a couple of edge cases to look out for such as handling null values, nullable types and adding a default entry to the drop down but this should give you the bare bones to get started.

Nested bean : a collection inside an object

I got a simple POJO class that i wish to display / update in a form
Using the BeanItem class and the binding of component data, i was able to quickly display the first attributes of may data class. However i've hit a wall for tow related attributes :
my class posses a set of available status, as a list of object 'AppStatus'. it also possess a current status, that is one of the status in the 'available' list.
I would like to display the list in the form as a combobox, with the current status selected.
I'we managed to associate the 'available' attribute with a combobox, but i can't seem to be able to fill this combobox when setting the data source (method setItemDataSource). How do i get the avalaible status list and the current status from my Item ?
I could always use a workaround and add a parameter to the method to get the source objet in addition to the BeanItem, but i would prefer to avoid this if the Item properties can give me my attribute.
Regards
Edit : shortened exemple, with code from Eric R.
class Status {
String id;
Sting label
+ setter /getter
}
class App {
String AppId;
String AppLabel
ArrayList<Status> availablestatus;
Status currentStatus
+setter/getter
}
in the form extension, in the createField of the fieldfactory i added the following lines
if ("status".equals(propertyId)) {
// create the combobox
ComboBox status = new ComboBox(
texts.getString("application.label.status"));
status.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
status.setItemCaptionPropertyId("label");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
IndexedContainer container = new IndexedContainer(
(Collection<ApplicationStatus>) item.getItemProperty(
"availableStatus").getValue());
status.setContainerDataSource(container);
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
return status;
} else...
this didn't work, i do get a combobox, with the correct number of lines, but all empties.
i tried to use a beanContainer instead of a IndexedContainer
BeanContainer<String, ApplicationStatus> container =
new BeanContainer<String, ApplicationStatus>(ApplicationStatus.class);
container.addAll((Collection<ApplicationStatus>) item
.getItemProperty("availableStatus").
container.setBeanIdProperty("id");
the result is slightly better, since i do have the available values in the combobox.
only the currentValue is not selected...
I also tried to use a nestedbean property to get the id of the currentstatus, but the result is still not valid... i get a combobox, with the correct value selected, but i can not see others values anymore, since the combobox is readonly ?(even with setReadOnly(false);)
I suggest my way to resolve this. I don't think this is the nicest way, but it's works.
The beanItem class contains all you need.
I did the following in a simple project and it's work verry well :
ComboBox status = new ComboBox("ComboBox");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
for(Status st : (Collection<Status>)item.getItemProperty("availableStatus").getValue()) {
status.addItem(st);
status.setItemCaption(st, st.getLabel());
}
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
Hope it's works.
Regards Éric
From the vaadin demo site you can get this sample that show how to fill a combobox with countries. You could do the same i would guess (not sure I understand your problem 100%):
myForm.setFormFieldFactory(new MyFormFieldFactory ());
private class MyFormFieldFactory extends DefaultFieldFactory {
final ComboBox countries = new ComboBox("Country");
public MyFormFieldFactory () {
countries.setWidth(COMMON_FIELD_WIDTH);
countries.setContainerDataSource(ExampleUtil.getISO3166Container());
countries
.setItemCaptionPropertyId(ExampleUtil.iso3166_PROPERTY_NAME);
countries.setItemIconPropertyId(ExampleUtil.iso3166_PROPERTY_FLAG);
countries.setFilteringMode(ComboBox.FILTERINGMODE_STARTSWITH);
}
#Override
public Field createField(Item item, Object propertyId,
Component uiContext) {
Field f = (Field)item;
if ("countryCode".equals(propertyId)) {
// filtering ComboBox w/ country names
return countries;
}
return f;
}
}

How to MapFrom a collection? Auto mapper

I have a linq to sql object that has some references to some other tables
I am trying to map it to a vm but nothing ever gets captures.
Mapper.CreateMap<A, G>();
// A is the linq to sql object
A.MyList // this is a collection that I am trying to get the value out of A.MyList.Id
// G is my View Model
public class G
{
public string AMyListId {get; set;}
}
vm = Mapper.Map<List<A>, List<G>>(aListOfAFromDb);
This always comes back from null. I thought I would have to do it manually so I tried
Mapper.CreateMap<A, G>().ForMember(dest => dest.AMyList , opt => opt.MapFrom(src =>????));
but since I am getting it from a list it won't give any of the properties to choose from.
Edit
I realized that I should not have a list of "MyList" it should be a one to one. I still am having problems trying to do what I want to do.
I have this
Mapper.CreateMap();
A.Vip.UserId // again my linq object is A
// G is my View Model
public class G
{
public string IsVip {get; set;}
}
vm = Mapper.Map<List<A>, List<G>>(aListOfAFromDb);
Mapper.CreateMap<A, G>().ForMember(dest => dest.IsVip, opt => opt.AddFormatter<VipFormatter>());
public class VipFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
bool value = (bool)context.SourceValue;
if (value)
{
return "Yes";
}
return "No";
}
}
yet nothing every gets bound. I am not sure why. Do I have to do change my property to "AVipUserId"? Or somehow tell it to map?
From what I can see in your code, and in addition to my comment above, you don't need AutoMapper for this one:
List<A> dbItems;
IEnumerable<G> results = dbItems.Select(x => x.MyList.MyListID);
In fact, you can't map A to G, because you're going to create multiple "G" objects for each "A" object.
Let me know if I misunderstood the question here.
UPDATE:
I would change "G" to use a boolean property and then do the following:
Mapper.CreateMap<A, G>().ForMember(dest => dest.IsVip, opt => opt.MapFrom(src => src.Vip == null));
Or, whatever logic you use to determine if it is a VIP or not.
How about:
List<G> items = // whatever
var result = items.Select(g => Mapper.Map<G, A>(g));

Resources