MVVM-Cross + Xamarin - Having Trouble with mvx CreateViewControllerFor() - c#

I am developing an IOS application with Xamarin using MvvmCross.
I want to use a tab bar to navigate between my Views. I want to have only 1 storyboard per View and want to navigate and call my views from code.
I've overridden the MvxStoryboardViewsContainer Method CreateViewOfTyp() like the following:
protected override IMvxTouchView CreateViewOfType(Type viewType,
MvxViewModelRequest request)
{
var storyboardAttribute = viewType.GetCustomAttribute<FromStoryboardAttribute>();
if (storyboardAttribute == null) {
return base.CreateViewOfType(viewType, request);
}
string storyboardName = storyboardAttribute.StoryboardName ?? viewType.Name;
return
(IMvxTouchView)
UIStoryboard.FromName(storyboardName, NSBundle.MainBundle).InstantiateInitialViewController();
}
Evrytime I try creating my tab bar I call this Method a try to create a View from my model.
The Problem I am encountering here is that UIStoryboard I instantiate is of type UIViewController and not MvxViewController (therefor the app crashes when it tries to cast).
The actual Controller in question however, should be an MvxViewController!
[FromStoryboard("WorklistView")]
public partial class WorklistViewController :
MvxViewController<WorklistViewModel>
{
public WorklistViewController (IntPtr handle) : base (handle)
{
}
}
I'm not sure what i'm missing? Why is the Controller i get a UIViewController and not the MvxViewController it should be?
Thing is I found a lot about what i am trying to do but i can't figure out what i'm doing different.

I found my mistake and it was quite a stupid one.
My designer class had a different namespace then my controller; god knows why...
Well, it's solved now.

Related

What is MauiContext and how to get one in .NET MAUI project?

I am trying to write a custom method for showing native dialog fragment on Android containing the MAUI ContentPage. I am using a partial class with partial method for that:
public partial void ShowCustomDialog(ContentPage contentPage)
{
if (contentPage.ToPlatform(-MauiContext-) is Android.Views.View androidView)
{
var customDialogFragment = new CustomDialogFragment();
if (customDialogFragment != null && Platform.CurrentActivity is Activity currentActivity)
{
customDialogFragment.Container.AddView(androidView);
customDialogFragment.Show(currentActivity.FragmentManager.BeginTransaction(), null);
}
}
}
The problem is, that the IElementToPlatform() method takes MauiContex as an argument, but I have no idea what is that, and how to recieve one?
I found only one example here: https://vladislavantonyuk.azurewebsites.net/articles/Creating-a-bottom-sheet-using-.NET-MAUI
But problem is that when I am trying to get the MauiContext from the Handler of the view (in my example the ContentPage), the Handler of that view is always null.
Do you have any relevant info about the MauiContext and how to implement it right in my solution please?

How is Setup class instantiated in MVVMCross in Xamarin?

I'm starting learning MVVM cross, In the android app, I have a splash screen class:
[Activity(MainLauncher = true,
Label = "#string/app_name",
Theme = "#style/Theme.Splash",
NoHistory = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen : MvxSplashScreenActivity
{
public SplashScreen() : base(Resource.Layout.SplashScreen)
{
}
}
and this is the Setup class:
public class Setup : MvxAndroidSetup
{
protected Setup(Context applicationContext) : base(applicationContext)
{
}
protected override IMvxApplication CreateApp()
{
return null;
}
}
the problem is that the debugger doesn't hit the constructor of the Setup Class, instead I get "An unhandled exception" after the constructor of the splash screen
EDIT
I've already defined the App class in the PCL project:
public class App : MvxApplication
{
public override void Initialize()
{
base.Initialize();
}
also defined the AppStart:
public class AppStart : MvxNavigatingObject, IMvxAppStart
{
public async void Start(object hint = null)
{
//hardcoded login for this demo
//var userService = Mvx.Resolve<IUserDataService>();
//await userService.Login("gillcleeren", "123456");
ShowViewModel<MainViewModel>();
}
}
The main reason behind this project is to understand the sequence of code required and executed by MVVM Cross, so I provide the minimum code till it runs successfully without runtime errors.
Update
I have read your code again more thoroughly and I can see the issue now. You defined the constructor of the Setup class as protected, which makes it invisible for activation.
On MvvmCross for Android the magic happens inside MvxAndroidSetupSingleton class (see the source code here) which searches for the Setup type you defined. The FindSetupType method looks for your defined Setup class first and then inside the CreateSetup method Activator.CreateInstance is used to build the Setup instance. The CreateInstance method variant used however searches only for public constructors, which means it doesn't find your protected one. The result is that it cannot build the Setup class and crashes.
Original answer
The reason this happens is that you have no Core libary that would define the MvvmCross App class and would initialize other required setup. I suggest you to start with a simple tutorial or to look into the official sample projects to see what is necessary to make MvvmCross work in a Xamarin.Android app.

Xamarin.Forms: Use of GetMainPage()

I'm currently reading the navigation section from An Introduction to Xamarin.Forms. One should use the GetMainPage() method. But how should that be used?
The default implementation of the app delegate looks like the following:
Applicaton Delegate:
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
}
App:
public class App : Application
{
public App ()
{
MainPage = GetMainPage ();
}
public static Page GetMainPage()
{
var mainNav = new NavigationPage(new ListExample());
return mainNav;
}
}
I got it managed to use the GetMainPage()method instead of getting
Application windows are expected to have a root view controller at the end of application launch
If I look into the (old?) examples (example1, example2) the app delegate is different and a CreateViewController() method is available. In my case it is not!
What is the correct way of loading the root page on to the stack?
You don't have to use GetMainPage(); that's just a method you create. The way X.Forms works these days is: it exposes a MainPage property in the Xamarin.Forms Application class. You set this to an instance of a Page. How you create that page is up to you. You can either use
this.MainPage = new ContentPage { Content = ... }
or you create one file per page (which IMHO is best for maintainability):
this.MainPage = new MyLoginPage();
or you use helper methods which create your pages:
this.MainPage = this.GetMainPage();
The main page is the first page of your Forms application. You can set the MainPage property to a different value to show another page.
Earlier versions of Forms used different approaches and not all samples have been updated yet. Now all platforms only need a call to the Forms Init() method and a call to LoadApplication() instead of creating a view controller, an activity or a page (WP8).

MonoTouch.Foundation.MonoTouchException has been thrown Objective-C exception thrown. Name: NSInternalInconsistencyException

I seem to be getting this issue whenever I run my iOS app within Xamarin.
MonoTouch.Foundation.MonoTouchException has been thrown
Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Could not load NIB in bundle: ’NSBundle ... (loaded)' with name ‘RouteMeViewController'
I am trying to replace a GoogleMapsViewController with a RouteMeViewController using the Objective C library and Binder in an app that I was given to work on. My AppDelegate looks like this:
namespace ExampleApp.iOS
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
RouteMeViewController viewController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
viewController = new RouteMeViewController ();
window.RootViewController = viewController;
window.MakeKeyAndVisible ();
return true;
}
}
RouteMeViewController
namespace ExampleApp.iOS
{
public partial class RouteMeViewController : UIViewController
{
RMMapView MapView { get; set; }
public RouteMeViewController () : base ("RouteMeViewController", null)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
MapView = new RMMapView(View.Frame, new RMOpenStreetMapSource().Handle);
MapView.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
if (UIScreen.MainScreen.Scale > 1.0)
MapView.AdjustTilesForRetinaDisplay = true;
Add (MapView);
}
}
}
Any help or direction is much appreciated, thank you!
It seems you're missing a designer file in the resources of your solution. Even if you programmatically create controls and views, you need a designer file where they need to be drawn in, even if it's just an empty designer file. For IOS, you can use XCode for that. You can create files with the .xib extension. They will be compiled on your device, and the resulting file has the extension .nib. Make sure the target of the .xib file is the correct viewController of your project, else you'll still get the error.
I hope this helps. Good luck!
It is 2023 and this problem still appease for xamarion.IOS
and working fine for xamarion.Android
I'm using the last xamarin forms version 5.0.0.2545
this answer solves my problem
I put a lot of codes after InitializeComponent();
to solve the problem just put MainPage = new MainPage(); directly after InitializeComponent(); in your App.xaml.cs
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
The problem now it will directly go to the page without reading the other codes
so to solve this move your code to another page,empty page or splash screen page and put your code in it like
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MySplashScreenPage());
}

System.NullReferenceException creating viewModel

So, I'm trying to find the Umbraco node (as iPublishedContent), and pass it to the viewModel (as Ш've hijacked a route). So i put this in my controller:
private AddCouponCodesViewModel viewModel;
public AddCouponCodesController(){
//Get iPublished content
IPublishedContent content = Umbraco.TypedContent(1225);
//Pass to viewModel
viewModel = new AddCouponCodesViewModel(content);
RouteData.DataTokens["umbraco"] = content;
}
public ActionResult Index()
{
//return view etc
}
But I'm getting
Exception Details: System.NullReferenceException:
Object reference not set to an instance of an object.
here:
Source Error(AddCouponCodesViewModel.cs):
Line 20:
Line 21: }
Line 22: public AddCouponCodesViewModel(IPublishedContent content)
Line 23: : base(content)
Line 24: {
AddCouponCodeRenderModel.cs:
public class AddCouponCodesViewModel : RenderModel
{
public string test { get; set; }
public List<string> tables { get; set; }
public List<string> errors { get; set; }
public AddCouponCodesViewModel(IPublishedContent content, CultureInfo culture) : base(content, culture)
{
}
public AddCouponCodesViewModel(IPublishedContent content)
: base(content)
{
}
And this is the Global.asax
public class Global : UmbracoApplication
{
protected override void OnApplicationStarted(object sender, EventArgs e)
{
base.OnApplicationStarted(sender, e);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//AreaRegistration.RegisterAllAreas();
//WebApiConfig.Register(GlobalConfiguration.Configuration);
//FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//RouteConfig.RegisterRoutes(RouteTable.Routes);
base.OnApplicationStarting(sender, e);
RouteTable.Routes.MapRoute(
"AddCouponCodes", // Route name
"Admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "AddCouponCodes", action = "Index", id = "" } // Parameter defaults
);
}
}
The content is published (I've checked and double checked), and the node ID is correct.
What I'm basically trying to do here, is to get the route example.com/Admin/{controller}/{action}/{parameter}
To be routed, but having problems connecting it with the umbracoNode (And class RenderModel requires a iPublishContent object as a parameter, but I'm in no luck when trying to pass it anything)
Could someone please help me here, been stuck way too many hours on this :-(
To clarify, if you are hijacking a route, it means that you are overriding the way Umbraco passes it's RenderModel to one of it's published pages. You can either do this globally by overriding the main RenderMvcController, or you can override on a DocumentType-specific basis. So for example, if I have a Homepage doc type, I could create:
public HomepageController : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
// Create your new renderModel here, inheriting
// from RenderModel
return CurrentTemplate(renderModel);
}
}
This would route all calls to the homepage through this one action. For this, you don't need to define any new routes in the route table. And you should override the render model in the action not in the constructor.
Your question is slightly confusing and it's not entirely clear what you are trying to achieve because:
You have defined a route, and
In your constructor you are calling Umbraco.TypedContent(1225) to retrieve a specific published node
So ... if the admin page you are trying to route has itself been published by Umbraco (and it doesn't sound like it has), the just create a new controller with the name of the page's document type and override the render model in the way described above.
However ... if your admin page hasn't been published by Umbraco and you just want the admin page to access node data, then you have a couple of options:
Create a surface controller, inheriting from SurfaceController. This will give you access to the Umbraco context et al; or
Create a standard controller (preferrably in an Area) and inject the ContentCache using something like Autofac
E.g.:
builder.RegisterControllers(typeof (AdminController).Assembly)
.WithParameter("contentCache", UmbracoContext.Current.ContentCache);
Create a standard controller (preferrably in an Area) and access the node using Umbraco's ContentService API, i.e. new Umbraco.Core.Services.ContentService().GetById(1225)
The difference between the last two approaches is that:
Injecting the ContentCache provides you readonly but very quick access to the published content.
Accessing the ContentService provides you read/write access to the nodes themselves but at the expense of speed as you are querying the database directly.
It depends on what your requirement is.
Either way, it is well worth taking time to read through the documentation for hijacking Umbraco routes, and at least trying to understand what is going on.
Well, I can tell you that your view isn't getting fed anything for the Razor markup because your Index method doesn't feed it anything. That's one problem. I can also tell you, that in your AddCouponCodesViewModel, you'll need an empty constructor, so that the razor syntax can just create an instance, and then populate it to match your submitted object to the view.
Modify your ViewController :
public ActionResult Index()
{
return View(viewModel);
}
Modify your AddCouponCodesViewModel to add an Empty constructor:
public AddCouponCodesViewModel()
{
}
Create a paramaterless constructor on your view model like this:
public AddCouponCodesViewModel():
this(new UmbracoHelper(UmbracoContext.Current).
TypedContent(UmbracoContext.Current.PageId))
{
}
This will get the contexts your other constructors are looking for.
After you've created a class with specific constructors, the compiler stops generating a parameterless one by default. Since you need a parameterless constructor, this is how to get one and still pass in the Umbraco contextual info your viewmodel needs

Categories

Resources