Problem with binding to MvxView with Xamarin.iOS and MvvmCross - xamarin.ios

I have a problem with implementing following scenario using Xamarin.iOS and MvvmCross (6.2.3.0). I have a view and in it's viewmodel I have a property which is a list of objects. I need to dynamically generate a label and a textfield for each of this list entry on my view. So I decided to implement a custom view for this. Here is what I tried so far:
In my viewcontroller, in CreateView I simply add my custom UIView. I can see it's content on my view. In ViewDidLoad() I create bindings:
private void CreateBindings()
{
var set = this.CreateBindingSet<MyController, MyViewModel>();
set.Bind(myCustomControl).For(x => x.DataContext).To(vm => vm.MyListOfObjects);
set.Apply();
}
MyCustomControl code is as follows:
public class MyCustomControl : MvxView
{
public MyCustomControl() {
//DataContext is always null here!
//I'd like to get access to list of objects here, add controls for each entry and make sure they are binded to a viewmodel's list of objects somehow.
}
}
I noticed that the list of objects in my viewmodel is set later than a constructor call for MyCustomControl is being made, so it makes sense that DataContext in MyCustom control is null. I'm missing something obvious I believe. Can someone point me in a proper direction? I would be very grateful.
I tried this example, which is exactly what I'm trying to achieve, but no luck so far ;(
N=32 - ViewModels and MvxView on the iPad - N+1 days of MvvmCross
https://www.youtube.com/watch?v=cYu_9rcAJU4&list=PLR6WI6W1JdeYSXLbm58jwAKYT7RQR31-W&index=35&t=1649s

Take a look at 23:43 of the video you posted.
You should do the view binding inside a DelayBind method.
this.DelayBind(() => {
var set = this.CreateBindingSet<MyCustomControl, MyItemViewModel>();
set.Bind(badgeLabel).For(v => v.Text).To(x => x.BadgeText);
set.Bind(topSummary).For(v => v.Text).To(x => x.TopSummary);
set.Bind(bottomSummary).For(v => v.Text).To(x => x.BottomSummary);
set.Bind(this.Tap()).For(v => v.Command).To(x => x.Edit);
set.Apply();
});
MvvmCross will do the binding for you.
Also Mind that the proper types have to be passed to CreateBindingSet.
On a side node, MvvmCross N+1 videos are from 2013 and some things has changed since then.
You can find some good examples there but sometimes it just won't work anymore.
If you are new to MvvmCross, download the source code of Playground project for reference:
https://github.com/MvvmCross/MvvmCross/tree/develop/Projects/Playground
and join the #mvvmcross slack channel if you need more help
https://xamarinchat.herokuapp.com/
MvvmCross is really great... once you grasp the basic concepts.

Related

New Action Definitions on a custom screen

I am trying to implement the new action definitions on a custom screen in 2020R1 in preparation for 2021R2. I cant seem to get these actions to group into folders, and the last action will not hide. What am I missing?
This is in the graph definition:
public override void Configure(PXScreenConfiguration graph)
{
var context = graph.GetScreenConfigurationContext<NGBiopsyEntry, NGBiopsy>();
context.AddScreenConfigurationFor(screenConfig =>
screenConfig
.WithActions(actions =>
{
actions.Add(a=>a.ActionSubmitBiopsy);
actions.Add(a=>a.ActionRefreshWorkTickets,
config=>config
.InFolder(FolderType.ActionsFolder));
actions.Add(a=>a.ActionStartPGTMTesting,
config=>config
.InFolder(FolderType.ActionsFolder));
actions.Add(a=>a.ActionSetEmbryoIDGen,
config=>config
.InFolder(FolderType.ActionsFolder)
.IsHiddenAlways());
}));
}
But still I get this:
Even after restarting the application, rebuilding the dll, publishing the customization project. Is there another setting I Am missing?
The new workflow is pretty entertaining to sort out. There was a great presentation at Summit this year, so keep an eye out for when the post the video to get a lot of great additional detail. This should be a pretty easy fix, but it isn't universal so I'll share the 2 ways you might have to add your menu actions.
The first thing that got my attention is that your menu actions are not in the Actions folder as you specified. That's a pretty clear sign that the context isn't quite right. I break mine into 2 parts, and I think you set yours with a single method.
In both cases, you need to start by overriding Configure, specifying the graph and primary DAC. Then you define a Configure using your WorkflowContext specific (again) to your graph and primary DAC. After that, things start to become a little more complicated.
Is this for a new custom screen that has no screen configuration yet, or are you extending a workflow that already exists? In the first example, I am adding a couple of print actions to the POReceiptEntry graph which already has a workflow (and screen configuration) defined. Also, I'm adding my action from the POReceiptEntry_Extension class, so I need to specify that on the Add.
public override void Configure(PXScreenConfiguration config)
=> Configure(config.GetScreenConfigurationContext<POReceiptEntry, POReceipt>());
protected virtual void Configure(WorkflowContext<POReceiptEntry, POReceipt> context)
{
context.UpdateScreenConfigurationFor(screen =>
{
return screen
.WithActions(actions =>
{
actions.Add<POReceiptEntry_Extension>(
g => g.printItemLabel,
a => a.InFolder(FolderType.ActionsFolder)
);
actions.Add<POReceiptEntry_Extension>(
g => g.printSOLabel,
a => a.InFolder(FolderType.ActionsFolder)
);
});
});
}
When adding an action to a custom screen, the screen configuration is not created until you create it. In this example, you will see UpdateScreenConfigurationFor is changed to AddScreenConfigurationFor. Also, you will see that Add does not specify the extension this time because the actions are defined in my custom (base) graph.
public override void Configure(PXScreenConfiguration config)
=> Configure(config.GetScreenConfigurationContext<MyGraph, MyDAC>());
protected virtual void Configure(WorkflowContext<MyGraph, MyDAC> context)
{
context.AddScreenConfigurationFor(screen =>
{
return screen
.WithActions(actions =>
{
actions.Add(
g => g.myAction,
a => a.InFolder(FolderType.ActionsFolder)
);
});
});
}
Notes to other readers:
I'm using 2021R1, and for everyone else's benefit, I've talked with Kyle and learned he is using 2020R1. His specific implementation is a bit more complex, and he needed to add
.AddDefaultFlow(workflow => workflow...
to create his default workflow. This can be done in the screen definition as well from the screen editor in the customization project, although I cannot recall if our SysAdmin did that or if AddScreenConfiguration in my example handled it for me.

How can I know the retained class name or keyword when I use navigation framework in Android Studio?

The following code is from the project at https://github.com/mycwcgr/camera/tree/master/CameraXBasic
The project use the latest navigation framework, I find there are some retained class name such as CameraFragmentDirections, GalleryFragmentArgs.
The system have no prompt information for these class name, must I remember these keywords by myself?
Code
/** Method used to re-draw the camera UI controls, called every time configuration changes */
#SuppressLint("RestrictedApi")
private fun updateCameraUi() {
// Listener for button used to view last photo
controls.findViewById<ImageButton>(R.id.photo_view_button).setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
CameraFragmentDirections.actionCameraToGallery(outputDirectory.absolutePath))
}
}
/** Fragment used to present the user with a gallery of photos taken */
class GalleryFragment internal constructor() : Fragment() {
/** AndroidX navigation arguments */
private val args: GalleryFragmentArgs by navArgs()
}
No you do not need to remember these things by yourself, if you know of a trick.
For example, if you don't remember the "keyword" Directions, but you know you want to do something related to CameraFragment, you can start typing e.g. CameraFragm in Android Studio. It will then suggest CameraFragment and CameraFragmentDirections for you. That way you can find CameraFragmentDirections easily even though you did not remember the keyword Directions.
There are not that many keywords to worry about though. After working with the Navigation framework for a while, you will remember them all.
If you are curious, you can find the generated classes here after a build:
./app/build/generated/source/navigation-args/...
e.g. after a debug build:
./app/build/generated/source/navigation-args/debug/com/android/example/cameraxbasic/fragments/CameraFragmentDirections.java
If you are even more curious, the code that generates these classes is here:
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/navigation-safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/java/JavaNavWriter.kt
There you can for example find this code:
internal fun Destination.toClassName(): ClassName {
val destName = name ?: throw IllegalStateException("Destination with actions must have name")
return ClassName.get(destName.packageName(), "${destName.simpleName()}Directions")
}
which is the code that decides what name CameraFragmentDirections gets. (Note "${destName.simpleName()}Directions" at the end.)

Pluralsight Advanced - Movie Content not displayed in Content table

I am walking through Pluralsight Advanced Orchard course.
I just created the Movie module and created a sample movie.
It was working fine but I did notice that the sample movie did not show up in Manage Content page.
I can only get to the list by going to Content Definition and select "List Items"
Then I can see the list of Movie item
This is what I got so far. I followed the steps and don't see what I have missed. I did notice that Orchard has changed slightly from 1.4 to 1.10 appearance wise. I wonder if this also has something to do with the version difference.
Any tips would be appreciated! Thank you
namespace Pluralsight.Movies {
public class Migrations : DataMigrationImpl {
public int Create() {
ContentDefinitionManager.AlterTypeDefinition("Movie", builder=>
builder.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("AutoroutePart")
.WithPart("BodyPart")
.Creatable()
.Draftable());
return 1;
}
public int UpdateFrom1()
{
ContentDefinitionManager.AlterTypeDefinition("Movie", builder =>
builder.WithPart("BodyPart", partBuilder=>partBuilder.WithSetting("BodyTypePartSettings.Flavor", "text")));
return 2;
}
}
Try to add .Listable() into your type definition. Note that if you already ran those migrations, unless you reset the database, it won't execute again, so you'll have to put it into a UpdateFrom2() method.
Note that this setting can also be checked from the content definition screen after the fact.
Note: I think the PluralSight course was written at a time when this setting didn't exist, and everything was listable.

How to bind IReadonlyReactiveList to ReactiveCollectionView

I have a view model that retrieve a list of data from rest service, and store inside a property.
private readonly ObservableAsPropertyHelper<IReadOnlyReactiveList<Customer>> _searchResultCustomer;
public IReadOnlyReactiveList<Customer> SearchResultCustomer => _searchResultCustomer.Value;
I am trying to do a binding between SearchResultCustomer to ReactiveCollectionView like this this.OneWayBind(ViewModel, vm => vm.SearchResultCustomer, v => v.cvResult.DataSource);.
Without surprise it doesn't work, and the type expected from v.cvResult.DataSource is IUICollectionViewDataSource.
How do I solve this, is there any available example for Xamarin.IOS? Thanks :)
Binding to a UI collection, such as UITableView or UICollectionView, works a bit differently. Conveniently, you don't even need to create your own UICollectionViewSource. Just create a custom cell with an initialize method and bind like this:
ViewModel.WhenAnyValue(vm => vm.SearchResultCustomer).BindTo<Customer, CustomerCell>(collectionView, cell => cell.Initialize());
Credit to Paul Reichelt at this thread who answered a similar question, except for UITableView.
I forked his example project and added a working example for UICollectionView. I use IReactiveList instead of IReadOnlyReactiveList, but you should be able to modify it to fit your needs. Here's the source. Hope it helps.

Orchard Custom User Part is not stored in Database

I can't seem to store additional data in a separate contentpart attached to User. I have done the following:
Created a module
In the module I created a Model for ProfilePart and ProfilePartRecord
In the migration I created a table for ProfilePartRecord (from type ContentPartRecord)
In the migration I altered the typedefinition for User, by setting WithPart ProfilePart
I created a driver class, that has 2 edit methods, one for get and one for post (code snippets are below
I also created a handler that adds a storage filter for profilePartRepository of type ProfilePartRecord
Module Structure
Drivers/ProfilePartDriver.cs
Handlers/ProfileHandler.cs
Models/ProfilePart.cs
Models/ProfilePartRecord.cs
Views/EditorTemplates/Parts/profile.cshtml
Migrations.cs
Placement.info
Since I think the issue is in the Driver. This is my code:
Is it going wrong because the part is attached to User? Or am I missing something else.
public class ProfilePartDriver:ContentPartDriver
{
protected override string Prefix
{
get { return "Profile"; }
}
//GET
protected override DriverResult Editor(ProfilePart part, dynamic shapeHelper)
{
return ContentShape("Parts_Profile_Edit", () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/Profile", Model: part, Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ProfilePart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
I have used Skywalker's blog. There is one chapter about registering customers by using the User and adding your own content parts to it. Worked nice for me.
First of all - is your ProfilePart editor shown at all when you go to Dashboard and edit a given user? I noticed you're using Parts_Profile_Edit as a shape key, but actually use EditorTemplates/Parts/Profile.cshtml as a template. It's perfectly correct, but note that Placement.info file uses shape keys, so you have to use Parts_Profile_Edit as a shape name in there. Otherwise it won't get displayed.
Second - have you tried debugging to see if the second driver Editor method (the one for handling POST) is being called at all?
Like Bertrand suggested, I'd look into one of the existing modules that work (afaik there is one for user profile in the Gallery) and see the difference. It might be something small, eg. a typo.

Resources