Execute MVVMCross value converter logic on UI thread - xamarin

I am working on a Xamarin.iOS project, and have a MVVMCross value converter that takes a file extension string and returns the corresponding file icon as an UIImage.
This converter is used in a file list. I found that when I'm scrolling the list, as soon as the MvxTableViewCell is recycled, the UI converter is often invoked in the background thread for the recycled cell, making my code to throw an error complaining calling a UIKit method from a background thread.
It looks like in the latest version of MVVMCross, the old IMvxMainThreadDispatcher.RequestMainThreadAction method is obsolete. The documentation recommends to use IMvxMainThreadAsyncDispatcher.ExecuteOnMainThreadAsync method.
However, this method returns a Task with no generic type. This obviously can't work with the Convert method of the value converter which expects the UIImage.
Is there a way to configure MVVMCross to always invoke the converter on UI thread?
Update:
Take the following code example:
public class FileIconConverter : MvxValueConverter<string, UIImage>
{
protected override UIImage Convert(string fileExtension, Type targetType, object parameter, CultureInfo culture)
{
// When this is called, it could be in a background thread.
// Since UIDocumentInteractionController.FromUrl requires to be called
// on the UI thread, it would throw the UIKitThreadAccessException.
return UIDocumentInteractionController.FromUrl(NSUrl.FromFilename("/tmp/generic" + fileExtension)).Icons[0];
}
}

Since you have not added any code here all I can do is give you a bit of speculative advice.
What you can do is use the InvokeOnMainThread method of Xamarin.iOS
When you check the Xamarin.iOS documentation on Working with the UI Thread in Xamarin.iOS
You can see that you use this method to make the changes that are supposed to be made on the UI
InvokeOnMainThread ( () => {
// manipulate UI controls
});
Update
First of all, if you check the code for IMvxMainThreadDispatcher.RequestMainThreadAction at the MvvmCross's Git it is internally calling the same API!
So what you can do to your existing code is something like
public class FileIconConverter : MvxValueConverter<string, UIImage>
{
protected override UIImage Convert(string fileExtension, Type targetType, object parameter, CultureInfo culture)
{
UIImage image;
IMvxMainThreadAsyncDispatcher.ExecuteOnMainThreadAsync(()=>{
image=UIDocumentInteractionController.FromUrl
(NSUrl.FromFilename("/tmp/generic" + fileExtension)).Icons[0];
});
return image;
}
}

Related

MessagingCentre Does it pass object by value or by reference?

With reference to .Net Maui: How to read/write (get/set) a global object from any content page (MVVM)
I now have a need to pass an object that is rather a large (>500Mb), it is an OpenMap extract, a RouterDb object (Itinero http://docs.itinero.tech/index.html)
Though I am trying to use MVVM some packages (Mapsui) use code behind so I am left with a mixture of ViewModel and Codebehind. I can overcome the limitation of not having any kind of global object reference by using the MessagingCenter which works remarkably well.
I am wondering if this mechanism can handle the passing of large objects (megabytes) or if internally it simply passes a reference to the object. I suspect from my experiments that it is trying to pass a copy of the object and failing, as I end up with a null object.
Here's the code
MessagingMarker.cs - just a placeholder class
namespace RouteIt.Models;
public class MessagingMarker
{
}
In my MainPageViewModel.cs
//Takes about 10seconds to load
using (var stream = new FileInfo(#"C:\Users\Gordon\source\repos\RouteIt\Resources\Raw\gb.routerdb").OpenRead())
{
routerDb = RouterDb.Deserialize(stream);
}
//send message to codebehind containing object or reference?
MessagingCenter.Send(new MessagingMarker(), "RouterDbLoaded", routerDb);
and in my receiving MainPage.xaml.cs constructor
MessagingCenter.Subscribe<MessagingMarker, RouterDb>(this, "RouterDbLoaded", (sender, arg) =>
{
routerDb = arg;
});
router = new(routerDb);
Any thoughts? (And yes perhaps I need to re-struture my app, but at this stage I don't want to really, one day Mapsui might support mvvm apparently.)
As always thanks for at least reading :)

How can I code a class to receive a property for use with custom renderers?

I have seen this coding style:
public CustomTextCell()
{
}
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
"IsChecked", typeof(bool), typeof(CustomTextCell),
defaultValue: false);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
and this:
public class ExtViewCell : ViewCell
{
public bool NoTap { get; set; }
}
Can someone help explain the difference. Is one serving a different function from the other? In my case all I need is to pass to a custom renderer the value of NoTap. Should I code it like in the first or second example?
The second one is a POCO - a plain old C# object - that is relatively self-explanatory, but serves not much more purpose that holding data - and not that much in this case.
The first one is a bit more interesting, especially in the context of MVVM. SetValue does more than just setting the value, but will (in most cases) raise PropertyChanged event (see INotifyPropertyChanged), to notify subscribers that, well, a property has changed.
Now how does this relate to your custom renderer? You could implement the property in your view as a plain property - i.e. without notifications - and it might work (cannot tell, though, since I do not know your custom renderer) when setting IsChecked initially (and without binding). Anyway, imagine you'll update the value of IsChecked. You do so from your code and wonder, why this change is not reflected in your custom renderer. But how is your renderer supposed to know? Polling each and every property might be possible for smaller forms, but is a terrible waste of resources. (And Xamarin.Forms just does not work this way.) You'll page/view has to tell your custom renderer, that something has changed. INotifyPropertyChanged to the rescue. In your custom renderer you can subscribe to PropertyChanged event and react to IsChecked being changed, updating your native view.

rxBindings - How to know what consumer type should be when debouncing click events?

using rxBindings im trying to slow down a click event but i would like to know what the parameter is need.
For example, here is a call i am doing on a imageview. So ImageView v;
RxView.clicks(v)
.throttleFirst(400, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
#Override
public void accept(#io.reactivex.annotations.NonNull Object v) throws Exception {
showBottomSheet(getAdapterPosition());
}
});
but im im not sure what the parameter in accept should be ?
I was expecting i would get the view here but when i try changing the type to View i get an error of no such method.
If you look at the source code of the Observable generate using RxView.clicks(), you will see that when the click happens, the following code is triggered:
observer.onNext(Notification.INSTANCE);
that is defined in the library, as:
public enum Notification {
INSTANCE
}
It is just a convenient way for indicating that the event happened, it doesn't carry any extra information.

Xamarin Android two way communication between Javascript and C#

I am developing an app using Xamarin Android which has a WebView displaying a web page. I want to implement a two way communication between Javascript from WebView to c#. I could call C# from Javascript using this link. However i couldn't find a way to send data back from C# to Javascript.
Is there a way to send data back and forth in this approach. I thought writing a callback in Javascript would work but how to fire it from C# code.
Now, My problem is how to call WebView from a javascript interface class. I have a Javascript interface class as mentioned https://developer.xamarin.com/recipes/android/controls/webview/call_csharp_from_javascript/
namespace ScannerAndroid
{
public class JSInterface: Java.Lang.Object
{
Context context;
WebView webView;
public JSInterface (Context context, WebView webView1)
{
this.context = context;
this.webView = webView1;
}
[Export]
[JavascriptInterface]
public void ShowToast()
{
Toast.MakeText (context, "Hello from C#", ToastLength.Short).Show ();
this.webView.LoadUrl ("javascript:callback('Hello from Android Native');");
}
}
}
The code throws an exception at LoadUrl line. java.lang.Throwable: A WebView method was called on thread 'Thread-891'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {42ce58a0} called on null, FYI main Looper is Looper (main, tid 1) {42ce58a0})
Now i am struggling how to refer the WebView from this Java script interface class
Yes. That is possible. If you are targeting KitKat or higher you can use:
webView.EvaluateJavascript("enable();", null);
Where in this case enable(); is a JS function.
If you are targeting lower API levels you can use LoadUrl();:
webView.LoadUrl("javascript:enable();");
EDIT:
The error you get where it complains on LoadUrl is because it for some reason happens on a non-UI thread.
Since you have already passed on the Context into your JavascriptInterface class, then you can simply wrap the contents of ShowToast in:
context.RunOnUiThread(() => {
// stuff here
});
Just change signature from Context to Activity and it should help you marshal you back on UI thread.

Binding a textfield to a number with Griffon and JavaFX

I'm using Griffon 1.2.0 and JavaFX, and I'd like to bind a textfield to a number. I thought I'd be able to do it simply by defining the model property as a Float, but it doesn't seem to like that, even if I define a converter. I looked into the Validator plugin but that only seems to work for Swing (not JavaFX) - which is a shame because this presentation makes it look really good: http://www.slideshare.net/ecspike/introduction-to-griffon (page 67 shows exactly the kind of functionality I'd like).
In the meantime, I've just added a property change listener in the noparent block of my view as suggested here What is the recommended way to make a numeric TextField in JavaFX?:
amount2.textProperty().addListener({ ObservableValue<? extends String> observable, String oldValue, String newValue ->
try {
Integer.parseInt(newValue);
} catch (Exception e) {
observable.setValue(oldValue);
}
} as ChangeListener<String>)
This ensures the user can only enter numbers, but are there better options? I haven't found anything in the JavaFX space that rivals the Validation plugin functionality - should I give up on JavaFX and go back to Swing?
I thought this functionality would be already provided by GroovyFX's #FXBindable and bind() node, that is
class SampleModel {
#FxBindable float number
}
// View
textField(text:bind(target: model, 'number', converter: { v -> /* insert converter code here */))
considering that GroovyFX's bind() is a copy of SwingBuilder's bind().

Resources