How can I get NavigationService and navigate from a backdoor method? - xamarin.forms

I'm trying to navigate from a backdoor so that I can bypass a certain screen when running my UITests.
Here's my code:
AppDelegate.cs
[Export("bypassInitialGuidePage:")]
public NSString BypassInitialGuidePage(NSString noValue)
{
BackdoorHelpers.BypassInitialGuidePage();
return new NSString();
}
MainActivity.cs
[Export("BypassInitialGuidePage")]
public void BypassInitialGuidePage() => BackdoorHelpers.BypassInitialGuidePage();
BackdoorHelpers.cs
public static class BackdoorHelpers
{
#if DEBUG
public static void BypassInitialGuidePage()
{
new SettingsService().InitialGuideDone = true;
// Add navigation logic here
}
#endif
}
The BypassInitialGuidePage() method is being called successfully from the tests. However, I need help with getting the NavigationService so I can navigate away from current page.
I'm using Prism.Forms v7.1.0.431 and I've tried the solutions in https://github.com/PrismLibrary/Prism/issues/1032 but they are not working with the latest version.

Related

After creating a custom projinvoice can't find created design

I need some help here. I created a custom report invoice design for PSAProjInvoice.
I did duplicate PSAProjInvoice and worked on a already made design.
Created a Controller and PrintMgmtDocTypeHandler class.
Created outputitem extension and redirected it to my ProjInvoiceController
In axapta in ProjFormletterParameters form parameters it shows me name of my custom report but when I go to project invoices and try to make a look at the invoice I just get a error: Unable to find the report design PSAProjInvoiceSZM.ReportPL.
class PSAProjInvoiceSZM
{
[PostHandlerFor(classStr(PSAProjAndContractInvoiceController),
staticMethodStr(PSAProjAndContractInvoiceController, construct))]
public static void ReportNamePostHandler(XppPrePostArgs arguments)
{
PSAProjAndContractInvoiceController controller = arguments.getReturnValue();
controller.parmReportName(ssrsreportstr(PSAprojinvoiceSZM, Report));
}
}
I think that it's a problem with my controller class because I actually have no idea how it should look like. Tried to make one based on salesinvoice tutorial found on microsoft docs but it didn't help me at all.
Tried to make it based on this article:
https://blogs.msdn.microsoft.com/dynamicsaxbi/2017/01/01/how-to-custom-designs-for-business-docs/
My Controller:
class ProjInvoiceControllerSZM extends PSAProjAndContractInvoiceController
{
public static ProjInvoiceControllerSZM construct()
{
return new ProjInvoiceControllerSZM();
}
public static void main(Args _args)
{
SrsReportRunController formLetterController =
ProjInvoiceControllerSZM::construct();
ProjInvoiceControllerSZM controller = formLetterController;
controller.initArgs(_args);
Controller.parmReportName(ssrsReportStr(PSAProjInvoiceSZM, Report));
/* if (classIdGet(_args.caller()) ==
classNum(PurchPurchOrderJournalPrint))
{
formLetterController.renderingCompleted +=
eventhandler(PurchPurchOrderJournalPrint::renderingCompleted);
}*/
formLetterController.startOperation();
}
protected void outputReport()
{
SRSCatalogItemName reportDesign;
reportDesign = ssrsReportStr(PSAProjInvoiceSZM,Report);
this.parmReportName(reportDesign);
this.parmReportContract().parmReportName(reportDesign);
formletterReport.parmReportRun().settingDetail().parmReportFormatName(reportDesign);
super();
}
}

DryIoc and IServiceProvider on Prism for Xamarin.Forms (DryIoc.Microsoft.DependencyInjection)

I've got a Prism application with DryIoc as container.
I'd like IHttpClientFactory to provide HttpClients to my typed clients, which are like this:
public class ExampleService : IExampleService
{
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetExamplesAsync()
{
// Code deleted for brevity.
}
}
In App.xaml.cs I register my typed client so they can be injected in viewmodels with the following:
public partial class App
// ...
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.Register<IExampleService, ExampleService>();
}
And that's before trying to use IHttpClientFactory.
Now, to add it, we should AddHttpClient() on IServiceCollection. That's where I thought DryIoc.Microsoft.DependencyInjection was needed, so, still in App.xaml.cs, I wrote the following:
public partial class App
// ...
protected override IContainerExtension CreateContainerExtension()
{
var services = new ServiceCollection();
services.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
var container = new Container(CreateContainerRules())
.WithDependencyInjectionAdapter(services);
return new DryIocContainerExtension(container);
}
The problem is that in my ExampleService I'm getting client with the following specs:
{
"DefaultRequestHeaders":[
],
"BaseAddress":null,
"Timeout":"00:01:40",
"MaxResponseContentBufferSize":2147483647
}
whilst I expected BaseAddress to be https://api.example.com/, so the REST API call fails.
What is the correct pattern to use IServiceProvider when using Prism for Xamarin.Forms with DryIoc? Unfortunately there's no documentation or open source code available on the following matter, and I am kind of lost.
Thanks you, and have a great day.
UPDATE #1
As per kind Dan S. guidance, DryIoc.Microsoft.DependencyInjection was uninstalled so the project came back at its state before trying to use IServiceCollection dependencies (in my case, IHttpClientFactory), then I installed Prism.Forms.Extended and later Prism.DryIoc.Extensions.
After that CreateContainerExtension() in App.xaml.cs became:
protected override IContainerExtension CreateContainerExtension()
{
var containerExtension = PrismContainerExtension.Current;
containerExtension.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
return containerExtension;
}
and containerRegistry.Register<IExampleService, ExampleService>(); was removed from RegisterTypes().
Now ExampleService finally gets its HttpClient injected and everything is working.
UPDATE #2
The only packages related to Prism I am using are Prism.DryIoc.Forms and Prism.DryIoc.Extensions.
I completely removed the override of CreateContainerExtension() in App.xaml.cs and refactored RegisterTypes() to
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Code deleted for brevity.
containerRegistry.RegisterServices(s =>
{
s.AddHttpClient<IExampleService, ExampleService>(c =>
{
c.BaseAddress = new Uri("https://api.example.com/");
});
});
}
This way I get thrown a NotImplementedException.
However, by overriding CreateContainerExtension() with the following:
protected override IContainerExtension CreateContainerExtension() => PrismContainerExtension.Current;
Everything is finally back to working!
If you want to use IServiceCollection extensions such as AddHttpClient I would suggest that you use the Prism Container Extensions. In your case it would be Prism.DryIoc.Extensions. The Container Extensions provide a lot of additional support including support for registering services with via the Service Collection extensions.
You can either install Prism.Forms.Extended and it will all just work, or you can update your App as follows:
protected override IContainerExtension CreateContainerExtension() =>
PrismContainerExtension.Current;
Adding as this is the only post I've found in weeks of searching that explains how to do this.
I'm using Unity rather than Dryloc but the solution is the same.
Install ONLY these additional packages:
Prism.Forms.Extended
Prism.Unity.Extensions
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//Omitted Code
containerRegistry.RegisterServices(serviceCollection =>
{
serviceCollection.AddHttpClient<IApiService, ApiService>(client =>
{
client.BaseAddress = new Uri("Your Address Here");
});
});
}
public ApiService(HttpClient client)
{
//Do Stuff
}

Is it possible to delay-load PRISM / Xamarin Forms components that aren't immediately needed?

I have the following AppDelegate which takes quite some time to load:
Syncfusion.ListView.XForms.iOS.SfListViewRenderer.Init();
new Syncfusion.SfNumericUpDown.XForms.iOS.SfNumericUpDownRenderer();
Syncfusion.SfCarousel.XForms.iOS.SfCarouselRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfSegmentedControlRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfCheckBoxRenderer.Init();
new Syncfusion.XForms.iOS.ComboBox.SfComboBoxRenderer();
//Syncfusion.XForms.iOS.TabView.SfTabViewRenderer.Init();
new Syncfusion.SfRotator.XForms.iOS.SfRotatorRenderer();
new Syncfusion.SfRating.XForms.iOS.SfRatingRenderer();
new Syncfusion.SfBusyIndicator.XForms.iOS.SfBusyIndicatorRenderer();
What options should I consider when I know some of these components aren't needed for the main screen, but for subscreens?
I am using PRISM, and it appears that every tab is pre-loaded immediately before allowing display or interaction with the end user. What can I do to delay the pre-rendering that the Prism TabView does prior to showing the interface?
Should I use Lazy<T>? What is the right approach?
Should I move these components to another initialization section?
There are a number of ways you could ultimately achieve this, and it all depends on what your real goals are.
If your goal is to ensure that you get to a Xamarin.Forms Page as fast as possible so that you have some sort of activity indicator, that in essence says to the user, "it's ok I haven't frozen, we're just doing some stuff to get ready for you", then you might try creating a "SpashScreen" page where you do additional loading. The setup might look something like the following:
public partial class AppDelegate : FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App(new iOSInitializer()));
return base.FinishedLaunching(app, options);
}
}
}
public class iOSInitializer : IPlatformInitializer, IPlatformFinalizer
{
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterInstance<IPlatformFinalizer>(this);
}
public void Finalize()
{
new Syncfusion.SfNumericUpDown.XForms.iOS.SfNumericUpDownRenderer();
Syncfusion.SfCarousel.XForms.iOS.SfCarouselRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfSegmentedControlRenderer.Init();
Syncfusion.XForms.iOS.Buttons.SfCheckBoxRenderer.Init();
}
}
public class App : PrismApplication
{
protected override async void OnInitialized()
{
await NavigationService.NavigateAsync("SplashScreen");
}
}
public class SplashScreenViewModel : INavigationAware
{
private IPlatformFinalizer _platformFinalizer { get; }
private INavigationService _navigationService { get; }
public SplashScreenViewModel(INavigationService navigationService, IPlatformFinalizer platformFinalizer)
{
_navigationService = navigationService;
_platformFinalizer = platformFinalizer;
}
public async void OnNavigatedTo(INavigationParameters navigationParameters)
{
_platformFinalizer.Finalize();
await _navigationService.NavigateAsync("/MainPage");
}
}
If you're working with Modules you could take a similar approach though any Modules that would initialize at Startup would still be making that call to Init the renderers before you've set a Page to navigate to. That said, working with Modules does give you a number of benefits here as you only ever would have to initialize things that the app actually requires at that point.
All of that said I'd be surprised if you see much in the way of gain as these Init calls are typically empty methods only designed to prevent the Linker from linking them out... if you aren't linking or have a linker file you could simply instruct the Linker to leave your Syncfusion and other libraries alone.

What is the proper way to register view with viewmodels in Prism.DryIoc

I'm using the Prism Template Pack for Visual Studio for Mac to generate a new project (tried both shared and pcl) then updating to 7.0.0.340-ci. Is registration of the views to view models done by convention?
When I try to run this app it throws this exception: Objective-C exception thrown. Name: NSInternalInconsistencyException Reason: Application windows are expected to have a root view controller at the end of application launch.
Here is the code for the main app.
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("MainPage?title=Hello%20from%20Xamarin.Forms");
}
protected override void RegisterTypes(Prism.Ioc.IContainerRegistry containerRegistry)
{
Prism.Mvvm.ViewModelLocationProvider.Register<MainPage,MainPageViewModel>();
}
//protected override void RegisterTypes()
//{
// Container.RegisterTypeForNavigation<MainPage>();
//}
}
I had to comment out the bottom lines and redo the override due to incompatible signatures.
Where did the Container.RegisterTypeForNavigation go or what is it's replacement?
I also tried it without any code in the RegisterTypes method.
In a debug session exploring the NavigationService properties says MainPage is null.
The RegisterTypes method should look like this.
In prism forms 7.x pages used for navigation must be registered via RegisterForNavigation<>() or RegisterForNavigation().
protected override void RegisterTypes(Prism.Ioc.IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<MainPage,MainPageViewModel>();
}

Unit Testing code built with MVVMCross SQLite Plugin

I'm currently building a Xamarin.Forms project using MVVMCross. In order to test my platform specific code I am using Nunit.Xamarin which features an app that run tests on device.
This test app is a forms app but doesn't use MVVMCross and I haven't had any luck setting it up to use MVVMCross due to the fact the Application class loads an App of type NUnit.Runner.App whereas MVVMCross requires MvxFormsApp.
I want to test this class the saves and loads user data from an SQLite Database:
public class DataStorageService : IDataStorageService
{
private readonly SQLiteConnection _connection;
public User UserData
{
get { return _connection.Table<User>().FirstOrDefault(); }
set { _connection.InsertOrReplace(value); }
}
public DataStorageService(IMvxSqliteConnectionFactory factory)
{
_connection = factory.GetConnection(DataStorageConstants.LocalDatabaseName);
_connection.CreateTable<User>();
}
}
I want to actually test that it saves and loads from a local SQLite database so I don't want to mock the IMvxSqliteConnectionFactory. I tried installing MVVMCross and the SQLite plugin into the project and then passing in the Android implementation of the connection factory but that repeatedly threw a typeloadexception.
Any ideas as to how I can set up this test with MVVMCross (or are there alternatives?) and dependency injection?
It is possible :) The important stuff happens in the MvxSplashScreenActivity. The MvxFormsApp is basically empty. So we don't have to care. Example Code: https://github.com/smstuebe/stackoverflow-answers/tree/master/mvx-android-test-app
Create a nunit Test app project
Install-Package MvvmCross.StarterPack -Version 4.1.4
Get rid of Views folder
Install the SQLite plugin
Reference your Core project
Install-Package MvvmCross.Forms.Presenter -Version 4.1.4
Remove MainLauncher = true from MainActivity
Adust Setup to return your core project's App
protected override IMvxApplication CreateApp()
{
return new MyApp.Core.App();
}
Change SplashScreen to (source)
[Activity(MainLauncher = true
, Theme = "#style/Theme.Splash"
, NoHistory = true
, ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen
: MvxSplashScreenActivity
{
public SplashScreen()
: base(Resource.Layout.SplashScreen)
{
}
private bool _isInitializationComplete;
public override void InitializationComplete()
{
if (!_isInitializationComplete)
{
_isInitializationComplete = true;
StartActivity(typeof(MainActivity));
}
}
protected override void OnCreate(Android.OS.Bundle bundle)
{
Forms.Init(this, bundle);
Forms.ViewInitialized += (object sender, ViewInitializedEventArgs e) =>
{
if (!string.IsNullOrWhiteSpace(e.View.StyleId))
{
e.NativeView.ContentDescription = e.View.StyleId;
}
};
base.OnCreate(bundle);
}
}
Write a test like
[TestFixture]
public class TestClass
{
[Test]
public void TestMethod()
{
var service = Mvx.Resolve<IDataStorageService>();
Assert.IsNull(service.UserData);
}
}
Enjoy the awesomeness of MvvmCross

Resources