MvvmCross, platform-specific value converter not being invoked - android

I am using MvvmCross with Xamarin.Android. I have the Visibility plugin installed. In my Android application project, I have created an Android-specific visibility converter that supports the Invisible state (view not shown, but still taking up layout space):
public class VisibleOrInvisibleValueConverter
: MvxValueConverter<bool, ViewStates>
{
public ViewStates Convert(bool value, Type targetType, CultureInfo cultureInfo, object parameter)
{
MvxTrace.Error("VisibleOrInvisibleValueConverter.Convert");
return value ? ViewStates.Visible : ViewStates.Invisible;
}
}
In my .axml markup, I use this converter like this:
<FrameLayout
android:layout_width="0dip"
android:layout_height="5dip"
android:layout_weight="1"
android:background="#ff0000"
local:MvxBind="Visibility Selected, Converter=VisibleOrInvisible, FallbackValue=0" />
Based on the documentation at https://github.com/MvvmCross/MvvmCross/wiki/Value-Converters#referencing-value-converters-in-touch-and-droid, I believe that MvvmCross will automatically discover the existence of this value converter, since it is in my UI project.
At runtime, the bound value always takes on the fallback value, no matter what the value of Selected is. Based on the documentation at https://github.com/MvvmCross/MvvmCross/wiki/Value-Converters#referencing-value-converters-in-touch-and-droid, this means that either my binding source path is missing, or the value converter threw an exception.
Unfortunately, I think I can rule both of those out. For the first possibility, I tried replacing my custom VisibleOrInvisible converter with the stock MvvmCross Visibility converter, and it worked fine. (That is, the binding worked fine. The stock Visibility converter doesn't support my desired behavior, though.) Anyway, I think this shows that the source path (Selected) does exist.
For the second possibility, I've set a breakpoint in the Convert function of the VisibleOrInvisible converter, and it is never executed. I also added an MvxTrace call in there, and I never see the trace message.
Although my converter is supposed to be automatically discovered, I have also tried explicitly adding my platform-specific assembly to the list of assemblies that implement value converters by overriding the ValueConverterAssemblies property getter in Setup.cs:
protected override List<Assembly> ValueConverterAssemblies
{
get
{
var toReturn = base.ValueConverterAssemblies;
toReturn.Add(typeof (VisibleOrInvisibleValueConverter).Assembly);
return toReturn;
}
}
But this did not help.
I think that MvvmCross is discovering my converter OK. If I intentionally refer to a non-existent converter in my .axml file, I see exception messages in the debug trace at runtime. But when I specify my VisibleOrInvisible converter, these messages do not appear.
My working theory is that an exception is occurring in the process of invoking my converter, before the only line of code in the converter is executed. But I don't know how to get to the bottom of that. No exception messages appear in the debug trace.
Is there a simple step that I'm getting wrong? I've studied the MvvmCross ValueConversion example pretty carefully, and I think I'm doing everything that example does.

I've just taken the ValueConversion sample, upgraded the core csproj file to profile 158 and then inserted your value converter.
This converter was picked up fine - I could see it in the converter list using:
protected override void InitializeLastChance ()
{
base.InitializeLastChance ();
var registry = Mvx.Resolve<IMvxValueConverterLookup> ();
var f = registry.Find("VisibleOrInvisible");
Mvx.Trace ("Custom converter was found : {0}", f != null);
}
However, when I tried to use it I saw binding errors about enum/bool/value type mapping... so I can see there is some problem...
After a little digging, it seems the reason for this problem was because your ValueConverter implements an unusual public ViewStates Convert method, instead of overriding the base class Convert method. To fix this I changed the converter to:
public class VisibleOrInvisibleValueConverter
: MvxValueConverter<bool, ViewStates>
{
protected override ViewStates Convert (bool value, Type targetType, object parameter, CultureInfo culture)
{
MvxTrace.Error("VisibleOrInvisibleValueConverter.Convert");
return value ? ViewStates.Visible : ViewStates.Invisible;
}
}
For more on authoring value converters using the MvxValueConverter<...> helpers, see https://github.com/MvvmCross/MvvmCross/wiki/Value-Converters#using-the-mvxvalueconverter-helper (if there's some other sample or document somewhere that has the wrong sample, then "sorry" and please point it out to whoever owns it so they can fix it)
Further, you may find the source for MvxValueConverter helpful - it's hopefully pretty straight-forward to follow how it implements IMvxValueConverter: https://github.com/MvvmCross/MvvmCross/blob/v3.1/CrossCore/Cirrious.CrossCore/Converters/MvxValueConverter.cs
With that problem solved, the next challenge presented by this question is how to use the FallbackValue. I've not fully analysed the trace I saw from this problem but I did experiment with a few other FallbackValue syntax examples - and these all seemed to work correctly:
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="200dp"
android:background="#ff0000"
local:MvxBind="Visibility VisibleOrInvisible(ThisWillNotBeFound), FallbackValue=Visible" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="200dp"
android:background="#ff0000"
local:MvxBind="Visibility VisibleOrInvisible(ThisWillNotBeFound), FallbackValue=Invisible" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="200dp"
android:background="#ffff00"
local:MvxBind="Visibility VisibleOrInvisible(ThisWillNotBeFound, FallbackValue=true)" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="200dp"
android:background="#0000ff"
local:MvxBind="Visibility VisibleOrInvisible(ThisWillNotBeFound, FallbackValue=false)" />
I'm not sure if the numeric fallback value is working for this case currently - suspect it needs more investigation (will log as a potential issue).

Related

How do you access exposed variables and functions in a custom view class Kotlin Android

Ideally, the end result is a way to access a custom view's parameters and functions to manipulate whats being shown, the input validation status, etc. I suppose Im looking to access it similar to a standard class.
I have an app that implements a block of payment fields in multiple views throughout. Depending on a payment type dropdown it changes the block between a bank entry and a credit card entry input set. I want a single class to handle the input fields and validation otherwise there is a ton of duplicate code in these views.
I created a PaymentInputView class that extends ConstraintLayout and implemented all the text watchers, field validation method, live data variables and public functions. Seems silly at this point but how do I use it? I would expect that I should be able to access public variables and functions from within my fragment or activity that implemented the custom view but I am not finding that to be the case.
In the XML I added the custom view.
<com.example.customviews.PaymentInputView
android:id="#+id/payment_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="#dimen/border_padding"
app:layout_constraintTop_toBottomOf="#+id/payment_nickname_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
In the Fragment I attempted to use databinding to access the methods or functions of the class but that doesnt seem to work as expected.
binding.paymentView.showFields.value = SHOW_CREDIT_FIELDS
So maybe this is because im accessing it incorrectly? Try by using a class reference.
val payView: PaymentInputView = binding.paymentView
payView.showFields.value = SHOW_CREDIT_FIELDS
The PaymentInputView is a bit larger. Not much development or testing has been done here, I haven't been able to implement it. The gist is here
https://gist.github.com/baggednismo/fd47d4058fb79602d6328c91724911e0

Android MvvmCross bind Text to MvxCommand

I have an Android Application and use Xamarin and MvvmCross. I have a EditText and would like to call a command everytime the text changes.
The command in the VM:
public MvxCommand<string> SearchContactsCommand => new MvxCommand<string>(SearchContacts);
private void SearchContacts(string searchTerm)
{
// Do search
}
And this is the Edit box:
<android.support.design.widget.TextInputLayout
android:layout_marginTop="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxLang="Hint SearchPlaceholder"
local:MvxBind="TextChanged SearchContactsCommand" />
</android.support.design.widget.TextInputLayout>
I tried it with different variations of TextChanged without success.
If possible I would like to avoid having a property on the VM for the searchterm and execute the search in the setter of it.
Is there any way how I can achieve that or do I have to write a custom binding?
Version:
- MvvmCross 5.6.3
- MvvmCross.Binding 5.6.3
Currently there is no built in binding that can do that, you'll have to do a custom one. Here you can see almost all of the built bindings there are. The others are in Droid support plugins in the Mvx{NameOfTheDroidSupportPart}SetupHelper.cs files like MvxPreferenceSetupHelper.cs inside MvvmCross.Droid.Support.V7.Preference if you want to see them too.
I'm not sure what you are aiming with that but if you want to do an autocomplete you can use MvxAutoCompleteTextView. Here you have an example. And also look into this that is the binding the autocomplete uses to fire the change every time the value changes you just have to adapt it to trigger a command with the value as the parameter.
Any problem just ask and I'll be glad to help

Android Data Binding BindingAdapter: "Cannot find the setter"

This bound attribute is failing to build, with the error:
Cannot find the setter for attribute "errorText"
#BindingAdapter({"errorText"})
public static void setErrorText(TextInputLayout view, String error) {
view.setError(error);
}
<android.support.design.widget.TextInputLayout
android:id="#+id/email_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:errorEnabled="true"
app:errorText="#{data.usernameError}"
>
....
Why is this not working?
It appears that this error was being caused because the BindingAdapter was not being compiled, as a result of a source error elsewhere in completely unrelated code. That other source error (a typo so a variable being referred to was declared using a different name and so did not exist) was not clear in the build error log but once I saw it in the source code it was easy to fix and in turn fixed the BindingAdapter issue.
If you see this error, check for other possible build errors in the source and build logs.
Firstly, setErrorText is public so you do NOT need define in binding adapter.
It still works without define errorText in BindingAdapter class ( Confirmed ! )
If you want to define in binding adapter, you have to change as below:
#BindingAdapter("app:errorText")
Hopes this help !

Why does "enabled" and "pressed" attributes work for ImageButton?

Where to see a list of all attributes of all view types in Android?
I got an impression, that ImageButton does not have enabled and pressed attributes. At least, they didn't work when I set them in XML. Also I found a lot of "guides" on how to make these button either disabled and/or pressed.
Simultaneously, when I bound them with data binding
<ImageButton
android:id="#+id/locate_button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_toRightOf="#id/bookmark_button"
android:enabled="#{activity.locateEnabled}"
android:pressed="#{activity.locatePressed}"
android:onClick="#{activity.onLocateClick}"
android:src="#drawable/locate_selector"
android:background="#null"
/>
they just worked. Both pressed and enabled. At the same moment pressed is even reported as unknown property by Android Studion spell checked!
So, what is it?
1) by-design behaviour and I just don't understand something (what?)
2) sugar from data binding library
3) hacking
4) ????
How to know, how poratble is this feature?
The answer is both 1 and 2. Data binding will allow you use an attribute to call any setter on a View. You can look at the data binding guide's section on attribute setters.
When you set the android:enabled attribute, you are using Android data binding's the automatic setters to calls setEnabled(). Android data binding sees the attribute name (enabled) and looks for a setter with that name and finds setEnabled(). The same is true for android:pressed -- there is a setPressed() method. Yay for convention!
android:onClick is a real attribute, but data binding doesn't use it. There is no setOnClick() method, either. Instead, there is a Binding Adapter that sets an OnClickListener to call your onLocateClick() method. There is a bit of magic involved in this that relies on the Annotation Processor that data binding uses to examine your code, but suffice it to say that it does this at compile time instead of using runtime reflection.
All event listeners should have Binding Adapters written for them with the same name as the event (as opposed to the listener name). So, you can also set android:onLongClick, for example. You may also be interested in the lambda syntax for events.
I'm not sure what you mean by "portable." Data Binding is usable at least back to Gingerbread (we claim Froyo, but really, who targets Froyo?), but you won't be able to transfer it to iOS or anything like that. All of Android data binding is done with a small runtime library and generated code. You don't have to worry about specific versions of Android.

Default binding using Rio in MvvmCross

I have a list view which binds to an array of strings as such:
<Mvx.MvxListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource StringArray"
local:MvxItemTemplate="#layout/listitem_view" />
My item template is simply just a text view...
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
which bound fine using JSON...
local:MvxBind="{'Text':{'Text':''}}" />
and again using Swiss...
local:MvxBind="Text " />
but after updating Mvx to 3.10 I'm now not getting anything bound to my text view even though the list is still bound to the list view. Has the syntax in Rio binding changed the default behaviour?
There was no intended change to this area in 3.0.10 - although there was a fix for this Error when making bind ObservableCollection<string> for a MvxListView - fix was https://github.com/slodge/MvvmCross/commit/d325fb75eaeeb8e470e0ac551f2b69b441f7b285. I believe this was released and worked in 3.0.10 for a test app.
Has the syntax in Rio binding changed the default behaviour?
I don't know if anyone has seen any issues with these in MethodBinding or FieldBinding as part of the Rio extensions. I've not heard of any.
If this is broken - and depending on whether this is broken due to the binding engine (Tibet) or the Method and Field extensions (Rio) or something else, then possible workarounds include:
switching away from Rio
using the period "." alternative to empty space - e.g. local:MvxBind="Text ."
adding a public object HackSelf { get { return this; } property to the bound object and binding to that local:MvxBind="Text HackSelf"
overriding the registered binding engine (e.g. back to Swiss)
If this is broken - it might also be a good idea to log an issue on github for this - including modules loaded, use case, version used, any suspicious trace output seen and a link back here.

Categories

Resources