MvvmCross and view navigation - c#

I'm using MvvmCross for my application and starting with iPhone. To achieve the sophistication on navigating between views using RequestNavigate and Close I've derived a new presenter from MvxBaseTouchViewPresenter. So far so good.
The problem I'm having now is that it is possible to close or show a view while the previously visible view is still in transition, either opening or closing. The only way I could think of to work round this problem was to use the ViewDidAppear and ViewDidDisappear event handlers of view controllers to call an Action that does the showing of the next view thereby deferring the showing until after the previous view has finished its transition.
This means I have to add:
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
DoPostTransitionAction();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
DoPostTransitionAction();
}
private void DoPostTransitionAction()
{
if( _postTransitionAction != null)_postTransitionAction();
}
private Action _postTransitionAction;
public void ShowActionAfterTransition( Action postTransitionAction)
{
if( this.IsBeingDismissed || this.IsBeingPresented)
{
_postTransitionAction = postTransitionAction;
}
else
{
_postTransitionAction = null;
postTransitionAction();
}
}
to view controllers, introduce an IEventViewController interface to make me add the code and change the Show method of the presenter to be:
public override void Show(MvxShowViewModelRequest request)
{
// find the current top view as it will disappear as a result of
// showing the requested view
// note: this will be null for the first show request
var topViewController = TopViewController;
// if the request is to roll back an existing stack of views
// then rollback to the bottom of the stack
if (request.ClearTop && topViewController != null)
{
RollBackStack(topViewController);
}
// the top view is about to be 'covered' by the view requested
// if the top view is transitioning (appearing or disappearing)
// then wait for it to finish before showing the view requested
var disappearingViewController = topViewController as IEventViewController;
// if the top view is disappearing and it's of the 'event' type
// delay calling Show until after the view has disappeared
if( disappearingViewController != null)
{
// the top view has been requested to close and will fire the
// 'transition complete' event when it has
// register the action to perform when the top view has disappeared
disappearingViewController.ShowActionAfterTransition (() =>
{
Show (CreateView(request), true);
});
}
else
{
// show the view now as the top view is either not closing
// or, if it is, is not going to say when it does
// create a view controller of the type requested
Show(CreateView(request), true);
}
}
What I'd really like to have would be those event handlers in the base class. I wonder what are the chances without me setting myself adrift from the main development path? Partial classes might do it?

Related

Selenium Page Object Model: Best way to handle Return Pages for Modals

I'm currently working on the POM of a web app, that allows to open modals from the navigation bar. The navigation bar stays the same for every page you're on. Each modal can be opened from every page.
I have defined a page object for each modal. Also the navigation bar is a pageobject,
What would be the best way to return to the page, that the modal was opened from?
So for example, you are on the Page FooPage and open modal AboutModal. What is the best way to return to FooPage? It should also work for BarPage and other Pages.
My first approach was, that i define a BasePage Object, which only includes the webdriver and navigationbar. i extend every Page on the web app from this BasePage. Then i could do something like this:
Code for FooPage:
public class FooPage: BasePage
{
private NavigationBar NavBar;
public FooPage(IWebDriver driver): base(driver)
{
...
this.NavBar = new NavigationBar(driver);
}
public NavigationBar Navigate()
{
return NavBar;
}
...
}
public class NavigationBar
{
...
public openAboutModal(BasePage currentPage)
{
log.Info("Open About Modal");
Action.Click(NavigationBarElements.AboutButton);
return new AboutModal(Driver, currentPage);
}
}
public class AboutModal
{
...
protected BasePage ReturnPage;
public AboutModal(IWebDriver driver, BasePage returnPage)
{
...
this.ReturnPage = returnPage;
}
public BasePage CloseAboutModal()
{
return this.ReturnPage;
}
...
}
This is not practical and not intuitive, because we have to remember on which pageobject we currently are, when writing tests. Also only the methods from BasePage are available, which means we have to additionaly navigate back to the page we wanted to be on.
So instead of writing
public class ModalTests
{
[Test]
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal() // AboutModal
.doSomeStuff() //AboutModal
.CloseAboutModal(); //FooPage
}
}
we have to do
public void CheckAboutModal()
{
Login() // FooPage
.Navigate() //NavigationBar
.openAboutModal(new FooPage(Driver)) // AboutModal
.doSomeStuff() // AboutModal
.CloseAboutModal() // BasePage
.Navigate() //NavigationBar
.ToFooPage(); // FooPage
}
}
How can I return to the calling Page of the modal, without making Testwriting to complicated?
Rather than write your test as one giant method-chaining call, use variables whenever you need to refer back to a certain page model. Your test can simply become:
var foo = Login();
foo.Navigate()
.openAboutModal()
.doSomeStuff()
.CloseAboutModal();
// Continue your test after closing the modal
foo.SomeOtherOperation();
In cases like this, the modal doesn't need to return anything. The CloseAboutModal() method can be a void return type. Your test should understand the larger context in which the modal is being used, and create local variables appropriately in order to "return" back to the main page.

State is wiped out when page is refresh in blazor with fluxor

I am not sure how to handle this behavior, this is the Feature I have:
public class HolderFeature : Feature<HolderState>
{
public override string GetName() => "HolderState";
protected override HolderState GetInitialState() => new(holder: new ProductHolder(), persons: string.Empty);
}
And, everything works fine until a page is refreshed (pressing f5), the state is wiped out, I have this to handle the error:
protected override void OnInitialized()
{
if (HolderState.Value.QuotedProduct.Quotes != null)
{
//do the logic
}
else
{
//show error screen
PressedF5 = true;
}
}
What I expect is that even when the page is refreshed it shouldn't wipe out the state. how can I do that?
The state is stored in memory. When you refresh the page you are unloading it and starting from scratch, so all the memory is lost if it isn't persisted somewhere.

Stopping Background Task On Page Navigation Back

I'm working on a Xamarin.Forms project that supports iOS and Android devices, and I'm using the MVVM design pattern.
I have navigation root page that consists of a ListView, when item is selected on this ListView, I execute the following command to Navigate to item details view.
Page DetailsPage = new View.DetailsView(SelectedItemData);
await Navigation.PushAsync(DetailsPage);
Once this Details Page is opened, I start running a background task.
private void StartBackgroundTask(){
TimerBackgroundTask = new Timer((o) => {
Device.BeginInvokeOnMainThread(() => Update()); }, null, 0, 1000);
}
}
Which is based on this class
public class Timer : CancellationTokenSource
{
public bool IsDisposed { get; set; }
public Timer(Action<object> callback, object state, int dueTime, int period)
{
System.Threading.Tasks.Task.Delay(dueTime, Token).ContinueWith(async (t, s) =>
{
Tuple<Action<object>, object> tuple = (Tuple<Action<object>, object>)s;
while (!IsCancellationRequested)
{
await System.Threading.Tasks.Task.Run(() => tuple.Item1(tuple.Item2));
await System.Threading.Tasks.Task.Delay(period);
}
},
Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
protected override void Dispose(bool disposing)
{
IsDisposed = true;
if (disposing)
{
Cancel();
}
base.Dispose(disposing);
}
}
Update function updates UI every 1 second.
Everything works fine and as it should, no issues here, however problems start to occur once I navigate back to root page, and back to details page - doing so twice causes the following error:
System.ArgumentException'jobject' must not be IntPtr.Zero. Parameter name: jobject
The problem stops occurring once the StartBackgroundTask gets disabled entirely from the code, so I believe that it is the one responsible for the error. Furthermore, I'm fairly convinced that this background task keeps on running somewhere in the thread even though I navigate back to the root page and I believe that if I could somehow dispose of the background task OnDissapearing event / navigation back button pressed, the error would no longer persist.
Unfortunately I have no idea how I how or even if its possible to somehow bind command to navigation back pressed event given my Views are bound to ViewModel.
Any tips would be greatly appreciated.
You can detect that a page is being dismissed by overriding OnDisappearing. In your DetailPage you could have something like this:
protected override void OnDisappearing()
{
TimerBackgroundTask?.Dispose();
base.OnDisappearing();
}

Removing event handlers in c#

I'm working on a Windows Forms app and I've come to a point where I can't understand what's happening.
I have something similar to an MVC architecture. Sometimes I want controls that belong to the view to stop listening to events. So inside the view code I've written a method that looks like this:
public void enableEventHandlers(bool enable)
{
if (enable)
{
control.someEvent += someEventHandler;
}
else
{
control.someEvent -= someEventHandler;
}
}
The thing is: when I want to remove an event handler I just call this method with false as a parameter.
If I call this method from inside the view code it works just fine. But if I call this method from inside the controller code, it doesn't work (the event handlers are not removed).
Just to give a little more context:
This works:
public partial class View : Form
{
public void enableEventHandlers(bool enable)
{
// The code I already showed
}
public void doSomething()
{
enableEventHandlers(false);
// do something
enableEventHandlers(true);
}
}
This doens't work:
public class controller
{
private View myView;
public void doSomething()
{
myView.enableEventHandlers(false);
// Do something... but somehow everything inside my Form is still paying attention to events
myView.enableEventHandlers(true);
}
}
Finally I found the problem. It seems that somehow I was attaching an event handler twice to the same Control. I couldn't find the exact line number where I was doing that anyway. The solution I found is to remove an event handler before adding a new one.
So the method enableEventHandlers looks now like this:
public void enableEventHandlers(bool enable) {
if (enable)
{
control.someEvent -= someEventHandler;
control.someEvent += someEventHandler;
}
else
{
control.someEvent -= someEventHandler;
}
}
Thanks for your answers.
I don't know if that's it but you didn't initialize your View. You just say "private View view", but that doesn't point to anywhere. You want to either make a new View by doing private View v = new View(), or let that view point to the view that you want to change the events.

MVP Navigation in WinForms

Have been learning about MVP and have tried writing a test app using it in WinForms. I'm struggling to find a well explained example on how to navigate between my forms/views. As an example, the program starts and I want to show a login dialog then go into my main view if the login was successful. At the moment, my Main method looks something like this:
static void Main()
{
var loginView = Injector.Resolve<ILoginView>();
if (loginView.DoLogin() != LoginResult.OK) return;
var mainView = Injector.Resolve<IMainView>();
Application.Run(mainView); // won't work as mainView isn't a form
}
The Injector object is just a wrapper around an IoC tool (currently StructureMap). The thing is, I've read that I shouldn't really be manually creating instances via the Injector as they should really be done via constructor injection.
I've managed to do this up to a point but not when it comes to navigation. I can't think of an elegant way of moving through my views and was wondering if anyone here might shed some light on this? I've read a little on application controllers but have not found an example to show it clearly.
In regards to the navigation question:
I've managed to do this up to a point but not when it comes to
navigation. I can't think of an elegant way of moving through my views
and was wondering if anyone here might shed some light on this? I've
read a little on application controllers but have not found an example
to show it clearly.
Below is a simplified version of a construct I've used. Note that the setup and tear down hooks are called automatically when the NavigateTo method is called. Also, +1 to #AlexBurtsev, as his answer hints at this very same approach.
// Presenter can and should offer common services for the
// subclasses
abstract class Presenter
{
public void Display()
{
OnDisplay();
}
public void Dismiss()
{
OnDismiss();
}
protected virtual OnDisplay() // hook for subclass
{
}
protected virtual OnDismiss() // hook for subclass
{
}
private NavigationManager _navMgr;
internal NavigationMgr NavigationManager
{
get
{
return _navMgr;
}
set
{
_navMgr = value;
}
}
}
// NavigationManager is used to transition (or navigate)
// between views
class NavigationManager
{
Presenter _current;
// use this override if your Presenter are non-persistent (transient)
public void NavigateTo(Type nextPresenterType, object args)
{
Presenter nextPresenter = Activator.CreateInstance(nextPresenterType);
NavigateTo(nextPresenter);
}
// use this override if your Presenter are persistent (long-lived)
public void NavigateTo(Presenter nextPresenter, object args)
{
if (_current != null)
{
_current.Dismiss()
_current.NavigationMgr = null;
_current = null;
}
if (nextPresenter != null)
{
_current = nextPresenter;
_current.NavigationMgr = this;
_current.Display(args);
}
}
}
class MainMenuPresenter : Presenter
{
private IMainMenuView _mainMenuView = null;
// OnDisplay is your startup hook
protected override void OnDisplay()
{
// get your view from where ever (injection, etc)
_mainMenuView = GetView();
// configure your view
_mainMenuView.Title = GetMainTitleInCurrentLanguage();
// etc
// etc
// listen for relevent events from the view
_mainMenuView.NewWorkOrderSelected += new EventHandler(MainMenuView_NewWorkOrderSelected);
// display to the user
_mainMenuView.Show();
}
protected override void OnDismiss()
{
// cleanup
_mainMenuView.NewWorkOrderSelected -= new EventHandler(MainMenuView_NewWorkOrderSelected);
_mainMenuView.Close();
_mainMenuView = null;
}
// respond to the various view events
private void MainMenuView_NewWorkOrderSelected(object src, EventArgs e)
{
// example of transitioning to a new view here...
NavigationMgr.NavigateTo(NewWorkOrderPresenter, null);
}
}
class NewWorkOrderPresenter : Presenter
{
protected override void OnDisplay()
{
// get the view, configure it, listen for its events, and show it
}
protected override void OnDismiss()
{
// unlisten for events and release the view
}
}
I haven't used WinForms for a long time, but I'll try to answer this. I would use the same strategy as WPF Prism do.
About MainView and Application.Run:
Create a main Region (root Form), with empty container inside which can hold UserControl (I forgot exact class names), then when you need to switch root view, you do RootView.SetView(UserControl view) which will do something like Form.Clear(), Form.AddChild(view).
About the navigation and using container:
You could create a service for navigation: INavigationService which you inject in constructors with method like INavigationService.NavigateView(String(or Type) viewName, params object[] additionalData)
You can insert a method in mainView that returns the actual form.Then you can call
Mainview:IMainView
{
Form GetView()
{
//return new Form();
}
}
In Main you can call ,
Application.Run(mainView.GetView())

Categories

Resources