MVVMCross changing selected tab bar item from within nested view controller - xamarin.ios

We're using MVVMCross within our application and I've come up against something that I'm not sure I've solved in the best way possible.
One of our ViewModels contains 3 other view models - a dashboard and 2 lists. In iOS this is presented using a MvxTabBarViewController which works great. Android and WP present this view in a similar manner. An example of the object model is below:
public class ProjectViewModel : MvxViewModel
{
public DashboardViewModel Dashboard {get;set;}
public FirstListViewModel FirstList {get;set;}
public SecondListViewModel SecondList {get;set;}
}
We're now in the situation where if a certain action happens within the DashboardViewModel we would like to instruct the navigation to change the tab in iOS and the same thing to happen on the other platforms.
The only way I've been able to get the tab to change on iOS is to use this.SelectedIndex = 1; from within the iOS ProjectView.
At the moment also the only way I've managed to trigger this change is to fire an event from the DashboardViewModel and then the ProjectViewModel subscribes to this and fires another event which is subscribed to by the ProjectView to instruct it to change the tab in whatever device specific way it needs to. I can't help but think there is a better way to do this.
I've tried taking a look at a custom ViewPresenter for iOS and calling ShowViewModel FirstListViewModel from within the DashboardViewModel but the presenter doesn't appear to be getting used so we just transition normally. My idea was I could get in the middle, cancel the navigation request and then flip the active tab on the ProjectView.
Any suggestions would be appreciated on how we could do this in a better cross platform way using MVVMCross to handle the change if at all possible.

You should be able to do this in any of several ways:
using a custom presenter with overridden Show as you suggest
using a custom presenter with overridden ChangePresentation - and using a custom hint
using a custom binding or a binding to a property within the ProjectView to drive the transition
using a custom IMvxInteraction property
using a custom event from VM to View
using a messenger to send a message from the ViewModels to the Views.
Ultimately lots of these could work and which of these I might choose would depend on which one worked and which one the team are happy with - shipping the working app is always the ultimate goal.
Given where I am with MvvmCross experience, I'd probably opt today for trying the approach of trying a custom IMvxInteraction property. But this might not be for everyone... it certainly might be overkill for this sample...
However, to do this, I would try:
add a public enum Display { Dash, First, Second } to the Core project
add a ProjectViewModel property:
private MvxInteraction<Display> _display = new MvxInteraction< Display >();
public IMvxInteraction<Display> DisplayChange { get { return _display; } }
whenever this ViewModel wants to fire the change it can fire it using e.g. _display.Raise(Display.First)
the ProjectView could then bind Display to its own property which might be implemented like:
private IDisposable _subscription;
private IMvxInteraction<Display> _displayInteraction;
public IMvxInteraction<Display> ChangeDisplay
{
get { return _displayInteraction; }
set
{
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
}
_displayInteraction = value;
if (_displayInteraction != null)
{
_subscription = _displayInteraction.WeakSubscribe(DoDisplayChange);
}
}
}
private void DoDisplayChange(Display which)
{
// change the tab display here
}
the binding would be added in ViewDidLoad like:
set.Bind(this).For(v => v.ChangeDisplay).To(vm => vm.DisplayChange);

Related

How to add a New Action to the mobile App

Good day
Build 20.107.0026
I have created a New Action and want to add it to my Mobile app. Is it possible to add a custom action to a mobile screen? I have created the below action in the Appointment screen(FS300200)
namespace PX.Objects.FS
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class AppointmentEntry_Extension : PXGraphExtension<AppointmentEntry>
{
#region Event Handlers
public PXAction<PX.Objects.FS.FSAppointment> DoWork;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "DoWork")]
protected void doWork()
{
}
#endregion
}
}
Mobile code below. If I want to add the button to the main menu; the 3 dots on the side do I use AppointmentRecords as the container?
update screen FS300200 {
update container "AppointmentRecords" {
add listAction "StartTravelAPICall" {
behavior = Void
displayName = "StartTravelAPICall"
}
}
}
The short answer is YES! The long answer depends in part on what version of Acumatica you are using. For the purpose of this answer, I'll assume you are in 2019R2 and already know how to add or edit a Mobile App screen in Acumatica. If not, the training guides referenced below should give you all the detailed information you need to accomplish your goal.
Manipulating the Mobile App screens/actions is relatively easy if the screen/action works in the browser interface. The T410 course material explains how to add an action in 2019R2. I don't work with Field Services, so I'll have to explain more generically as per the training guide.
First you must either add or edit the screen in the Mobile Application section of the Customization Project. (In your case, you want to Update the existing screen.) This will create a section of code in the customization project that looks like this:
As you can see, the original screen definition in the mobile app is shown on the right, and you will be updating the screen to add your action. You will need to add the appropriate container (not shown in your question) and then the action within that container.
To continue the answer, let's switch to the training guide example on page 12 of the T410 course updating the SO303000 screen. You can compare to your screen to see what needs to be changed.
add container "InvoiceSummary" {
# fields declaration
…
add recordAction "Save" {
behavior = Save
}
add recordAction "Cancel" {
behavior = Cancel
}
add containerAction "Insert" {
behavior = Create
}
add recordAction "ReleaseAction" {
syncLongOperation = true
behavior = Record
}
}
I believe your action would follow the ReleaseAction portion at the bottom of the example, and the need for syngLongOperation = true would depend on what your action is doing (i.e. if you need the action performed asynchronously).
Assuming your container is already defined in the page, which I suspect it is, let's instead look at the example for PO302000 on page 35. This example shows how to UPDATE a container to add your action.
update screen PO302000 {
update container "DocumentSummary" {
add recordAction "AddPOOrderLine" {
displayName = "Add PO Line"
behavior = Void
redirect = True
redirectToContainer = "AddPurchaseOrderLine$List"
}
}
}
That was a more complex action, but yours may be as simple as.
update screen FS300200 {
update container "ServiceOrderTypeLine" {
add listAction "DoWork" {
Behavior = Void
displayName = "Do Work"
}
}
}
If you need guidance on how to read the WDSL Schema to identify the container, etc. that training is found in T400 starting on Page 13.
I highly recommend reviewing both T400 and T410 if you are working with the mobile app as there is a lot more detail in those training guides than can be explained easily in a Stack Overflow post/answer.

Use NewClassWizard in kentico admin

I want to create a custom page that allows me to track manually every data modification made by kentico in the database
I understood that I can implement it by code by using the NewClassWizard. But when done how I can use it in Kentico admin ?
I try to load it from Page Type module but can find a way to specify my custom class
Is it the right way to use that? The documentation is not really clear on that
thanks for your help
I'd suggest creating a global event handler to track changes. While Kentico will already do a lot of this tracking for you, you can specifically add whatever specific events you want as a global event. This will handle any changes made on the customer-facing public website, inside the Kentico UI, and in any Kentico API calls.
In your custom global event handler, you can check for different types of events based on the object and the CRUD activity. For instance something like this:
using CMS;
using CMS.CustomTables;
using CMS.DataEngine;
using CMS.DocumentEngine;
// Registers the custom module into the system
[assembly: RegisterModule(typeof(CustomInitializationModule))]
public class CustomInitializationModule : Module
{
// Module class constructor, the system registers the module under the name "CustomInit"
public CustomInitializationModule()
: base("CustomInit")
{
}
// Contains initialization code that is executed when the application starts
protected override void OnInit()
{
base.OnInit();
// Assigns custom handlers to events
DocumentEvents.Insert.After += Document_Insert_After;
}
private void Document_Insert_After(object sender, DocumentEventArgs e)
{
if (e.Node.ClassName.ToLower() == "your.classname")
{
// create an event to log to a custom module table or a custom table so it's not logged directly in the Kentico Event log
CustomTableItem item = new CustomTableItem("customtable.name");
item.SetValue("ItemEventType", "Create document");
item.SetValue("ItemEventCode", "Your event code");
item.SetValue("ItemEventUserID", e.Node.DocumentModifiedByUserID);
item.SetValue("ItemEventUrl", e.Node.NodeAliasPath);
item.SetValue("ItemEventName", e.Node.DocumentName);
item.Insert();
}
}
}
This is a simple example of when a document is created. You don't have to check the page type's classname if you don't want to. You can also include a global event handler for any "object" like a cms_class object, just use that definition in your code <name>Info.TYPEINFO.Events.<eventtype>. This is not limited to any object, you can track transformation updates, page type field modifications, etc. You just need to know the object name/type and write code around it.

Warn the user that he is about to loose his change in Edit view when leaving to view to another in GWT

I want to prevent the user that he will loose his changes in an EditView when changing the view to another.
I use MVP4G in my project and the project is divided as mvp's structure (one package for the template another one for views ..) is there any solution to detect the EditView in the eventBus. or detect the current View shown to user
Thanks in advance
Thanks to the Navigation Event feature in mvp4g, the presenter will get control before the view changes. At this point the presenter can decide if the navigation will be done or not. This is the correct place in a mvp4g application to save your data.
First zu have to mark all events in the eventbus that will change your view with:
#Event(..., navigationEvent = true)
void goToPage1();
Next your presenters have to implement the NavigationConfirmationInterface and the requires confirm-method:
public class Presenter extends ... implements NavigationConfirmationInterface {
public void confirm(NavigationEventCommand event) {
//pseudo method to verify if the view has changed
if (isViewModified(){
//Window shouldn't be used inside a presenter
//this is just to give a simple example
if (Window.confirm("Are you sure you want to leave?")){
event.fireEvent();
}
} else {
event.fireEvent();
}
}
}
And the last thing to do, is to set the presenter of the current view to the confirmation presenter by calling:
event.fireEvent(false);
This is usually done when the presenter gets control.
You will find the documentation here:
https://github.com/FrankHossfeld/mvp4g/wiki/03.-Defining-EventBus#navigation-event
Thanks to MVP4G's team including El Hoss who gives me a hint to check the MVP4G's blog.. I've solved my problem by following this example
http://mvp4g.blogspot.com/2011/06/navigation-control.html

MvvmCross: Display a BTProgressHUD spinner immediately after ViewModel creation

I'm using the BTProgressHUD Xamarin component in an app that is built using MvvmCross. I'm currently working on the iOS version of the app. My ViewModels make several web service calls and expose an 'IsBusy' property, which the associated views are binding to, in order to show or hide the progress spinner. This is pretty much the way things are set up in the N=34 MvvmCross sample (https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-34-Progress) as well.
The problem is that in some cases a ViewModel must automatically call a service as soon as it is created. I tried to make the call in the Start() function of the ViewModel, but I noticed that the BTProgressHUD spinner does not show up on top of the view. I suspect that the problem is that BTProgressHUD must be displayed only after a view has been made visible, and probably this is not the case when ViewModel.Start() runs.
Has anyone encountered this before? Is there a simple way to run code in the ViewModel after the view has been made visible?
Thanks.
Is there a simple way to run code in the ViewModel after the view has been made visible?
The N=42 video - http://slodge.blogspot.co.uk/2013/11/n42-is-my-viewmodel-visible-can-i-kill.html - introduces an IVisible interface that you can add to your ViewModel - it's your job to call this from your View - but this is easy to do on each platform. For example,, on iOS it is done using ViewDidAppear/ViewDidDisappear in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-42-Lifecycles/Lifecycle.Touch/Views/FirstView.cs
protected IVisible VisibleViewModel
{
get { return base.ViewModel as IVisible; }
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
VisibleViewModel.IsVisible(true);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
VisibleViewModel.IsVisible(false);
}

Swap RootViewController with MVVMCross

I need to implement a login/logout using MVVMCross, iOS only to start. After the user logs in, I want to close the view and make the "real" first view the root controller. For logout, I want to do the same in reverse. Whenever the LoginViewModel is requested, clear the root and replace it.
This Remove ViewController from stack indicates there is a ClearTop parameter, but it looks like it is gone in v3?
I then found this What is the best way to handle GoBack for the different MvvmCross (v3) platforms and I implemented this Presenter:
public override void Close(IMvxViewModel toClose)
{
if (toClose is LoginViewModel)
{
ClearBackStack();
Show(new MvxViewModelRequest() { ViewModelType = typeof(FirstViewModel)});
return;
}
base.Close(toClose);
}
public override void Show(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof (LoginViewModel))
{
ClearBackStack();
}
base.Show(request);
}
Is this the correct way to handle this? Is there an easier mechanism (pre-v3 like)? Should I be overriding ChangePresentation instead?
Also, is there a mechanism to call ShowViewModel from a View? Do I need to resolve the IMvxViewDispatcher or is there a more straight forward method?
Yes, if you want to do custom presentation techniques then the easiest way is to implement your own view presenter.
For an introduction and some links on this, see How can I implement SplitView in another view in MvvmCross?
You are free to write code directly in your views, including navigation logic using resolved IoC objects. However, mvvmCross tries to encourage you to put this logic in the viewmodels - especially so that the 'logic' is more easily shared between platforms.

Resources