My MvvmCross app uses a custom presenter that creates a SplitView when run on an Ipad. Both master and detail contain a navigation controller. This works fine except that I don't know how to hint the system where I want the next view to show.
I have a couple of views that sometimes should be shown in the detail view and sometimes in the master. If run on an iPhone they will be shown in the single navigation controller.
So in the ViewModel I would like to hint where to put the next view. Something like
ShowViewModel(paramObject, ShowInMaster);
If run on an iPhone the ShowInMaster will be ignored.
Is this possible or am I perhaps doing this all wrong?
There's an optional presentationBundle parameter you can use in most of the ShowViewModel overrides - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross/ViewModels/MvxNavigatingObject.cs#L39
You can create a bundle simply from a Dictionary<string,string>() - e.g. you could use new MvxBundle(new Dictionary<string,string>() { { "ShowSplit":"true" } })
When used, this presentation bundle will get placed into the MvxViewModelRequest - in the public IDictionary<string, string> PresentationValues { get; set; } member - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross/ViewModels/MvxViewModelRequest.cs#L33
The request will then get passed to your UI presenter (aka the 'navigation service' in other frameworks) - and your custom code in the presenters on each platform can then decide what to do with these 'presentation' hints - e.g. it can override public override void Show(MvxViewModelRequest request) to inspect the presentation hint contents and to then do some custom split view display (see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Touch/Views/Presenters/MvxTouchViewPresenter.cs#L45 for the default behaviour)
If it helps, a simple split view display (using fixed logic rather than presentation hints) is in N=24 of http://mvvmcross.blogspot.com
Related
I am trying to do Android Compose navigation initiated by the ViewModel. This example description https://medium.com/#ffvanderlaan/navigation-in-jetpack-compose-using-viewmodel-state-3b2517c24dde and project https://github.com/Frank1234/ViewModelNavigationCompose suggest to do navigation from the View but based on the state that is kept in the ViewModel.
The description https://medium.com/#ffvanderlaan/navigation-in-jetpack-compose-using-viewmodel-state-3b2517c24dde contains activity diagram, that includes step 4 and step 5 in whose the View notifies the ViewModel that navigation has happened.
My question is - how this step 4 (notificiation by View) is implemented in the example code https://github.com/Frank1234/ViewModelNavigationCompose? I can not find this implementation. I.e. there is view https://github.com/Frank1234/ViewModelNavigationCompose/blob/master/app/src/main/java/nl/frank/vmnc/ui/content/ContentPage.kt but I don't see any code in this View that could catch the navigation event and that could notify the ViewModel about it.
If you look closely at the ContentPage code there are onClick methods defined as:
...
onClick = viewModel::onNextWithDelayClicked
...
What this means is that ContentPage receives a reference to viewModel and then calls the onNextWithDelayClicked() method inside the viewModel
I think you're confused by the shorthand notation used in onClick, it is equivalent to:
...
onClick = { viewModel.onNextWithDelayClicked() }
...
I have a project in which I need to log an analytics event whenever any View Controller (log the name of the View Controller) comes on screen.
I was trying to avoid littering all of my existing View Controller classes with call to the analytics SDK.
I tried making an AnalyticsViewController and all my View Controllers would subclass this View Controller, and then I add analytics event in AnalyticsViewController class's viewDidLoad method. But the problem with this approach is that AnalyticsViewController does not which child View Controller is the call coming from.
I am using Swift 3.0. I believe that Swift with its powerful language features should be able provide me with an abstraction of some sorts.
Is there any way through this problem without littering all the View Controllers?
You were on the right track. Making a UIViewController parent class is a good idea.
In viewDidLoad method you can just add this:
let className = NSStringFromClass(self.classForCoder)
It will give you the name of current loaded view controller and then you can use that name in your event to specify which view controller was actually loaded.
Edit: added example.
So your parent's viewDidLoad would look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let className = NSStringFromClass(self.classForCoder)
sendEvent(withViewControllerName: className)
}
The answer given by #JPetric is an amazing starting point. I just had to do a little modification to get it to work.
I've put this in my AnalyticsViewController to retrieve the name of the current subclass.
private func currentClassName() -> String? {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last
}
I'm using MvvmCross 3.0.12 in an iPad project. Currently, I'm getting a NPE in MvxViewControllerExtensionMethods.LoadViewModel, because touchView.Request is null. This is only happening in a view controller that is inheriting from MvxTableViewController; view controllers inheriting from MvxViewController load and display just fine.
I've set breakpoint in MvxTouchViewsContainer.CreateView -> MyTableViewController.Ctor -> MvxBaseTouchViewPresenter.Show -> MyTableViewController.LoadView; which have all referenced the same instance of the class. Then when I hit a breakpoint in ViewDidLoad, its a new instance and all of the properties including Request are null.
Is this a bug with Xamarin.iOS or am I doing something wrong?
This sometimes happens for view controllers like table, tab bar and collection.
It's caused, I think, by something in the Objective C base class init referencing the View - and thus triggering ViewDidLoad before the C# construction is fully complete. This is a bit like what can happen in 'pure' c# if a base class constructor references a virtual method.
To check this is occurring for your app, put 2 breakpoints in your app - one in the ViewController constructor and one in ViewDidLoad - if ViewDidLoad is fired first, then you know this is the case.
The only way around this I've found is to code around this by triggering a second ViewDidLoad call in the constructor.
public FirstView()
{
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (ViewModel == null)
return;
// ...
If it helps:
I believe I've talked about this in at least one N+1 - maybe N=25 - see https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-25-Tabbed/Tabbed.Touch/Views/FirstView.cs and http://mvvmcross.wordpress.com
The same type of thing happens in pure objective C code - eg see initialize UITableViewController with [super initWithStyle:UITableViewStyleGrouped]
I was wondering how the people who develop for here BlackBerry go about managing the screens in their app. The most common practice (and the one I'm using) seems to be just to instantiate and push the new screen from the current one. The other option I've seen is using actions in the Main Application class to do the transitions. How do you guys manage?
We have a ScreenManager class manages the display of screens. It contains a Hashmap which has Screen name -> MainScreen pairs,public methods for adding and showing a screen.
When our application starts up all the screens required are created and added to the ScreenManager class.
In the showScreen() method we get the reference to the appropriate MainScreen class. Then we use UiApplication.getUiApplication().popScreen(screen) to hide the current screen. If the screen has already been shown we simply use popScreen() to remove screens until we reach the screen we want. Otherwise we just pushScreen() to move the screen to the top of the pile.
Calls to using the UiApplication are contained within a synchronized(UiApplication.getEventLock()) block
This approach does the job for us. We can create all the screens once at the application startup so it does not need to be done over and over again during the course of the application.
I also just use the above method of pushing to the screen-stack however an addition to this I also pass references of screens within my stack as parameter to new screens added.
If you have any public methods (which might for example update the contents of the screen etc) these can be called from other screens within your stack using this kind of reference. E.g.
Screen 1
public class MyScreen1 extends MainScreen
{
private LabelField content;
public MyScreen1(){
content = new LabelField(“this is the original content”);
add(content);
}
public void UpdateScreen(String newContent){
content.setText(newContent);
}
private void PushScreen{
MyScreen2 screen = new MyScreen2( (MyScreen1)UiApplication.getUiApplication().getActiveScreen());
UiApplication.getUiApplication().pushScreen(screen);
}
}
Screen 2
public class MyScreen2 extends MainScreen
{
private MyScreen1 originalScreen
public MyScreen2(MyScreen1 originalScreen){
this.originalScreen = originalScreen
public MyScreen1 () {
LabelField content = new LabelField(“Screen1 will now be changed.”);
add(content);
UpdateScreen1();
}
private void UpdateScreen1(){
originalScreen.UpdateScreen(“This is new content”);
}
}
In this example, once MyScreen2 has been popped, the content of MyScreen1 will have changed. This is useful if you have a scenario where you display details of an object then push an edit screen for the object which would pop back to an older version of the object.
Here's an excerpt from a book I'm reading about application design with MVC:
Ideally, the view is so simple and
logic-free as to need virtually no
testing. Users (and developers before
users) can reasonably test the view by
simply looking at the pixels on the
screen. Anything else beyond pure
graphical rendering should ideally be
taken out of the view and placed in
the controller and model. This
includes, for example, the logic that
determines whether a certain button
should be enabled or grayed out at
some point.
what does the bold statement mean to you? what would this look like?
thanks,
rod.
The logic that decides when to enable or disable the button should be residing in the controller and simply calls a method e.g view.EnableContinueButton() to enable/disable the button on the page.
The actual code to enable/disable the button on the page itself should be implemented in the view e.g a EnableContinueButton() method then which calls something like btnContinue.Enable().
Simply put, the view should concern itself with the UI details (show/hide/enable/disable UI elements) and leave all business logic processing to the controller. In this way, the controller does not need to concern itself with the UI elements and the view works independently of the actual business logic.
e.g in the Controller,
public void ProcessOrder()
{
if (!controller.ValidateOrder(model.OrderNo))
view.EnableContinueButton(false);
else
// Process the order
...
}
and in the View
public void EnableContinueButton(bool enabled)
{
btnContinueButton.Enabled = enabled;
}
Frankly I haven't got much experience in MVC (implemented in one project a while back) but I hope the logic separation between controller and view is clear enough.
This is what that bold statement means to me:
The controller is going to be full of nested if statements
The model (or viewmodel) is going to be full of properties to help render the page specific ways, making the object graphs difficult to maintain.
While I think the analysis should not be made in the view, the condition should be set so the button only has to think - show or not show.
eg. only show the examinee details button if the examinee is male.
You either create a viewmodel property ShowExamineeDetails. The view will check if this is ture or not.
the ShowExamineeDetails = is examinee Male?
code should be in the controller.
As for testing, I am yet to find an app that "...needs virtually no testing..."