TextView Compound Drawable MvvmCross Binding - xamarin.android

TextView has some DrawableXXX properties like DrawableStart, DrawableEnd, DrawableTop, etc. to show a Drawable next to the TextView text. Is it possible to bind this property using MvvmCross? I've tried using local:MvxBind="Drawablename 'check'" in my binding but it fails showing an MvxBind error:
Failed to create target binding for binding DrawableName for check
If this binding is not implemented in MvvmCross then how would I go about doing this on my own? Is the method used by N+28 still the recommended way to add a custom binding?

There is not currently any MvvmCross define target binding for adding compound drawables. However, you can easily add your own. If you are using MvvmCross 5+ you can take advantage of the new typed custom binding classes. Note, the example below has set the drawable to be placed on the left, however, you can choose to place at any direction (or multiple).
public class CompoundDrawablesDrawableNameBinding : MvxAndroidTargetBinding<TextView, string>
{
public const string BindingIdentifier = "CompoundDrawableName";
public CompoundDrawablesDrawableNameBinding(TextView target) : base(target)
{
}
public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
protected override void SetValueImpl(TextView target, string value)
{
var resources = AndroidGlobals.ApplicationContext.Resources;
var id = resources.GetIdentifier(value, "drawable", AndroidGlobals.ApplicationContext.PackageName);
if (id == 0)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning,
"Value '{0}' was not a known compound drawable name", value);
return;
}
target.SetCompoundDrawablesWithIntrinsicBounds(
left: id,
top: 0,
right: 0,
bottom: 0);
}
}
Then register in your Setup.cs
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<TextView>(
CompoundDrawablesDrawableNameBinding.BindingIdentifier,
textView => new CompoundDrawablesDrawableNameBinding(textView));
}
Then in your XML you can use
local:MvxBind="CompoundDrawableName <<YOUR PROPERTY TO BIND TO>>"

Related

How to apply jackson's default behaviour in custom serializer?

Since Jackson's hibernate5-module not working for me. I'm trying to implement my own lazy property filter. I implemented custom annotation introspection successfully.
But when I apply my custom serializer, #JsonIgnoreProperties is ignored.
#Entity
class Call {
#OneToMany(mappedBy = "call")
#JsonIgnoreProperties("call")
List<CallEvent> events;
}
#Entity
class CallEvent {
#ManyToOne(fetch = FetchType.LAZY)
Call call;
}
public class LazyValueIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findSerializer(Annotated a) {
var yes = a.hasAnnotation(ManyToOne.class)
|| a.hasAnnotation(Basic.class)
|| a.hasAnnotation(OneToMany.class)
|| a.hasAnnotation(OneToOne.class);
if (yes) {
return LazyValueSerializer.class;
}
return super.findSerializer(a);
}
}
public class LazyValueSerializer extends JsonSerializer<Object> {
#Override
public boolean isEmpty(SerializerProvider provider, Object value) {
return value == null || !Hibernate.isInitialized(value);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject(value);
}
}
Explanation:
LazyValueIntrospector.findSerializer detects possible lazy properties.
Hibernate.isInitialized tells me the value is initialized or not.
gen.writeObject(value); writes if property is initialized.
The problem is gen.writeObject(value); method call is ignoring #JsonIgnoreProperties("call") annotation.
The question is:
How to apply #JsonIgnoreProperties("call") annotation in my custom serializer?
Ps: spring.jackson.defaultPropertyInclusion=non_empty property applied globally. Which allows isEmpty checking.
As we are ignoring only one property, try giving #JsonIgnore over the call property inside the CallEvent entity above Call.
Example:
#Entity
class CallEvent {
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
Call call;
}
Without actual example of where #JsonIgnoreProperties is ignored it is hard to say. But I think that the custom serializer would need to delegate to the original serializer and not replace it; there's quite a bit more that is needed to support various other features.
To do that you cannot register serializer the way like shown here, but instead replace it using one of methods in BeanSerializerModifier (and need to register that modifier with ObjectMapper) -- that way you get the "real" serializer to delegate to.
Usually implementations also need to implement createContextual() (from ContextSerializer) which needs to be passed to the original ("delegate") serializer.
You may want to have a look at how serializers are implemented in Hibernate module itself.
So, Since the question is how to apply jackson's own behaviors in the custom bean serializer. I found the answer for myself.
extend from BeanSerializerBase, not BeanSerializer.
override with* methods. Such as withByNameInclusion and withProperties
This way, Jackson calls appropriate methods when it's needed.
#Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer unwrapper) {
return new LazyBeanUnwrappingSerializer(this, unwrapper);
}
#Override
protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
return new LazyBeanSerializer(this, properties, filteredProperties);
}
#Override
protected BeanSerializerBase withByNameInclusion(Set<String> toIgnore, Set<String> toInclude) {
return new LazyBeanSerializer(this, toIgnore, toInclude);
}
#Override
public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter) {
return new LazyBeanSerializer(this, objectIdWriter, _propertyFilterId);
}
#Override
public BeanSerializerBase withFilterId(Object filterId) {
return new LazyBeanSerializer(this, _objectIdWriter, filterId);
}
#Override
protected BeanSerializerBase asArraySerializer() {
throw new RuntimeException("Array serializer no supported");
}

Hide UIView on view load - Xamarin iOS and MvvmCross

I'm trying to hide an ui element on view load. I'm using Xamarin iOS and MvvmCross (6.2.3.0). In my view, I create an uitextfield and add following binding:
public override void ViewDidLoad()
{
//adding uitextfield
var set - this.CreateBindingSet(MyViewConroller, MyViewModel>();
set.Bind(uitextfield).For("Visibility").To(vm => vm.FieldVisibility).TwoWay().WithConversion("Visibility");
}
And here is my viewmodel:
public MyViewModel : MvxViewModel
{
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
}
public override Task Initialize() {
FieldVisibility = false;
}
But when the view is loaded, my uitextfield is still visible. I tried to call RaisePropertyChanged, but it didn't help:
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
RaisePropertyChanged(nameof(FieldVisibility));
}
It works, when I change some other viewmodel property and simply call:
private string _otherProperty;
public string OtherProperty
{
get {return _otherProperty;}
set {
SetProperty(ref _otherProperty, value);
FieldVisibility = false;
RaisePropertyChanged(nameof(FieldVisibility));
}
but I'd like to have this uitextfield hidden on view load. Can you suggest any solution for that? I'd be very grateful for any hint. Thanks!!
We have strongly typed bindings nowadays for stuff like this:
set.Bind(uitextfield).For(v => v.BindVisible()).To(vm => vm.FieldVisibility);
You don't really need the visibility converter here, should work without it.
Remember to also call Apply() on your binding set.
Also TwoWay() won't do anything here, because there are no Binding Targets which allow for Two Way bindings for UIView visibility.

"Failed to create target binding for to" in MvvmCross .dialog

So I'm playing around with MvvmCross and Monotouch.Dialog in iOS and I am experiencing a binding issue when I do something that is pretty trivial - and in fact done almost verbatim in one of Stuart's n+1 videos.
Given the following view:
[Register("FirstView")]
public class FirstView : MvxDialogViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindings = this.CreateInlineBindingTarget<FirstViewModel>();
Root = new RootElement("Example Root")
{
new Section("Search")
{
new EntryElement("SearchString", "Search String").Bind(bindings, vm => vm.SearchString)
}
};
}
}
and this ViewModel:
public class FirstViewModel : MvxViewModel
{
private string _searchString = "search string";
public string SearchString
{
get
{
return _searchString;
}
set
{
_searchString = value;
RaisePropertyChanged(() => SearchString);
}
}
}
When navigate to this view, I get the following errors from Mvx:
2013-08-22 14:44:51.766 TestApp[11581:c07] MvxBind:Error: 2.02 Empty binding target passed to MvxTargetBindingFactoryRegistry
[0:] MvxBind:Error: 2.02 Empty binding target passed to MvxTargetBindingFactoryRegistry
[0:]
2013-08-22 14:44:51.869 TestApp[11581:c07] MvxBind:Warning: 2.10 Failed to create target binding for to
[0:] MvxBind:Warning: 2.10 Failed to create target binding for to
I'm not exactly sure why the binding is failing. If I set a break point in the "Get" for SearchString, I do in fact see it getting hit. Changing the value of the Entry Element however does not trigget the "Set".
Any thoughts?
Stuart's guess is correct:
All I needed to do was change my Setup class to inherit from MvxTouchDialogSetup.

MvvmCross: Binding a BaseView Property to a BaseViewModel Property

What I am trying to achieve here is a global loading indicator with MvvmCross.
From what I gathered so far it appears that I can implement this by using a BaseView and BaseViewModel. The BaseViewModel should contain an IsLoading property that the BaseView can Bind to. Therefore, I can set IsLoading to true in any of my ViewModels to cause the indicator to be displayed.
The BaseViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel
{
private bool _isLoading = false;
public bool IsLoading
{
get { return _isLoading; }
set { _isLoading = value; RaisePropertyChanged(() => IsLoading); }
}
private string _loadingMessage = "Loading...";
public string LoadingMessage
{
get { return _loadingMessage; }
set { _loadingMessage = value; RaisePropertyChanged(() => LoadingMessage); }
}
}
As for binding to the ViewModel, this issue was addressed here: https://stackoverflow.com/a/10930788
I was successfully able to listen to IsLoading by attaching to the PropertyChanged event, however, since this is not a "real" binding it will not fire if IsLoading is set to true before you attach to the event (Such as before the view is loaded).
Next I attempted to call AddBindings to attach to my BaseViewModel in order to create a real binding between the two properties, although this does not appear to work (no errors).
Here is what my BaseView currently looks like for iOS:
public abstract class BaseView<TViewModel> : MvxViewController where TViewModel : BaseViewModel
{
public TViewModel ViewModel
{
get { return (TViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
protected BaseView(string nib, NSBundle bundle) : base(nib, bundle)
{
}
private LoadingOverlay _loadingOverlay;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_loadingOverlay = new LoadingOverlay(View.Frame);
this.AddBindings(new Dictionary<object, string>()
{
{this, "{'IsLoading':{'Path':'IsLoading'}}"},
{_loadingOverlay, "{'LoadingMessage':{'Path':'LoadingMessage'}}"}
});
}
public bool IsLoading
{
set
{
if (value)
View.Add(_loadingOverlay);
else
_loadingOverlay.Hide();
}
}
}
Are there any pitfalls to AddBindings that I may be unaware of? or is there a newer method altogether that I should be using?
I Appreciate the help, Thanks.
If you are using v3, then by default you must change over to the new 'Swiss' style bindings.
These change Json text like:
"{'LoadingMessage':{'Path':'LoadingMessage'}}"
Into simpler text like:
"LoadingMessage LoadingMessage"
For more on this, see http://blog.ostebaronen.dk/2013/01/awesome-mvvmcross-swiss-bindings-for.html
One further option you can use in v3 is 'fluent bindings'. To see these in action, see all the iOS samples in the N+1 series - http://mvvmcross.wordpress.com/, or questions like Fluent Bindings and UIButton titles
Eg something like:
this.CreateBinding().For("LoadingMessage").To("LoadingMessage").Apply();
It may also help for debugging if you enable additional trace - see MvvmCross Mvx.Trace usage

Set Inner Dependency by Type using Structuremap

I have a structuremap configuration that has me scratching my head. I have a concrete class that requires a interfaced ui element which requires an interfaced validation class. I want the outer concrete class to get the default ui element, but get a concrete-class-specific validation object. Something like this:
class MyView
{
IPrompt prompt
}
class GenericPrompt : IPrompt
{
IValidator validator
}
class MyValidator : IValidator
{
bool Validate() {}
}
How can I configure structuremap with the Registry DSL to only use MyValidator when creating dependencies for MyView. (And assumedly using BobsValidator when creating dependencies for BobsView)
Are you getting MyView (and BobsView) from the container? Can we assume that they will all take an instance of IPrompt?
One approach would be to register all of your validators with a name that matches the names of your view. You could implement your own type scanner that just removes the Validator suffix:
public class ValidatorScanner : ITypeScanner
{
public void Process(Type type, PluginGraph graph)
{
if (!typeof (IValidator).IsAssignableFrom(type)) return;
var validatorName = type.Name.Replace("Validator", "");
graph.AddType(typeof(IValidator), type, validatorName);
}
}
Now, if you assume an IPrompt will always be requested by a View that follows that naming convention, your registry could look like:
public class ValidatorRegistry : Registry
{
public ValidatorRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.With<ValidatorScanner>();
});
ForRequestedType<IPrompt>().TheDefault.Is.ConstructedBy(ctx =>
{
var viewName = ctx.Root.RequestedType.Name.Replace("View", "");
ctx.RegisterDefault(typeof(IValidator), ctx.GetInstance<IValidator>(viewName));
return ctx.GetInstance<GenericPrompt>();
});
}
}
To retrieve your view with the appropriate validator, you would have to request the concrete type:
var view = container.GetInstance<MyView>();
Note that this will only work if you are retrieving your view with a direct call to the container (service location), since it depends on the "Root.RequestedType". Depending on how you plan to get your views, you might be able to walk up the BuildStack looking for a View (instead of assuming it is always Root).

Resources