Navigate to new navigation page from ViewModel with Xamarin XLabs - mvvm

I am trying to navigate to a new navigation page from a ViewModel
I have this snippet in my App.cs which makes a new navigation page and binds the ViewModel to the View
MainPage = new NavigationPage((Page)ViewFactory.CreatePage<MainViewModel, MainView>());
The snippet is from this article getting started xamarin forms labs xaml mvvm ioc # "5. Register the View with the ViewFactory"
But how would we do this in a ViewModel, and does it make sense to create a new page and bind the ViewModel and View together when we just binded them with ViewFactory.Register?
Snippet from the article:
public class App : Application
{
public App()
{
RegisterViews();
MainPage = new NavigationPage((Page)ViewFactory.CreatePage<MainViewModel, MainView>());
}
private void RegisterViews()
{
ViewFactory.Register<MainView, MainViewModel>();
}
}

No sure I undersant your question well but, here is what I do :
first in your AppDelegate (or main activity) register your Dependency Injection module (IDependencyContainer for a simpleContainer here):
var resolverContainer = new SimpleContainer();
UserService us = new UserService();
var app = new XFormsAppiOS();
resolverContainer.Register<IDevice>(t => AppleDevice.CurrentDevice)
.Register<IDisplay>(t => t.Resolve<IDevice>().Display)
.Register<IXFormsApp>(app)
.Register<IJsonSerializer, JsonSerializer>()
.Register<IDependencyContainer>(resolverContainer);
Resolver.SetResolver(resolverContainer.GetResolver());
Then on your app.cs, be sure to register the navigation service to goes whith your main navigation page
Page loginPage = new NavigationPage((LoginPage)ViewFactory.CreatePage<LoginViewModel, LoginPage>());
MainPage = loginPage;
Resolver.Resolve<IDependencyContainer>()
.Register<INavigationService>(t => new NavigationService(MainPage.Navigation)) //New Xlabs nav service
.Register<INavigation>(t => MainPage.Navigation); // old Xlabs nav service
And in your viewModel push to page you need
await Resolver.Resolve<INavigation>().PushAsync((ParameterPage)ViewFactory.CreatePage<ParameterViewModel, ParameterPage>(), false);

Related

Navigate to page on start in .NET Maui app

Seems like a simple question, but I haven't been able to find a simple answer. Essentially I want to choose which page in the app to start on based on some stored state. I added a GoToAsync call in the AppShell constructor, but this didn't work--which makes sense because the AppShell hasn't been fully constructed yet.
I found this answer, but it feels like it kind of skirts around the issue:
Maui AppShell - Navigate on Open
Where is the best place to inject some code that will run once on startup and can successfully navigate a .NET Maui app to a chosen page?
After playing around with overrides, it seems like overriding Application.OnStart works! Shell.Current is set at this point and navigation works.
Here's additional code that allows for asynchronous initialization and uses a Loading Page until the initialization is complete:
using MyApp.Services;
using MyApp.UI;
namespace MyApp;
public partial class App : Application
{
ConfigurationProviderService m_configProvider;
public App(ConfigurationProviderService configProvider)
{
m_configProvider = configProvider;
InitializeComponent();
MainPage = new LoadingPage();
}
protected override void OnStart()
{
var task = InitAsync();
task.ContinueWith((task) =>
{
MainThread.BeginInvokeOnMainThread(() =>
{
MainPage = new AppShell();
// Choose navigation depending on init
Shell.Current.GoToAsync(...);
});
});
base.OnStart();
}
private async Task InitAsync()
{
await m_configProvider.InitAsync();
}
}

Where is good place to register Messenger responsible for showing Windows to ensure MVVM pattern Separation of Concerns and Testability not violated?

Scenario:
MainWindow has a Menu About which relates to AboutWindow.
About Meny is triggered by command:
<MenuItem Header="_About" Command="{Binding OpenAbout}"/>
OpenAbout is property like that:
private RelayCommand _openAbout;
public RelayCommand OpenAbout
{
get
{
return _openAbout ?? (_openAbout = new RelayCommand(() => Messenger.Default.Send(new NotificationMessage("ShowAboutView"))));
}
}
Notification message is registered in App.cs class as follows:
static App()
{
DispatcherHelper.Initialize();
}
public App()
{
RegisterMessenger();
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, ProcessShowAboutView);
}
private void ProcessShowAboutView(NotificationMessage message)
{
AboutWindow view = new AboutWindow();
view.Show();
}
I analysed another questions like that:
How to open a new window using MVVM Light Toolkit
WPF MVVM - How to Show a view from MainWindowViewModel upon Clicking on button
I like Messenger functionality but however I am not sure If above solution is a good one.
I would be thankful for any advise!
As depicted above, Registering messages is done in App Config.
I consider it not be a good place therefore I need to know what place would be better.
Another place to consider would be Locator
I personaly would register the messages in App.xaml.cs in the OnStartup method (WPF) and in the set up method of the unit test (dont forget to unregister everything in the tear down method).

Rendering Partial View NopCommerce Plugins

I'm working on to write a new payment plugin on nopcommerce.
How can I render a new partialview in PaymentInfo.cshtml? The new view locates the same path with PaymentInfo.cshtml.
to render any view in the plugins for nopCommerce it must be embedded resource and it must be written in fully qualified name like
#Html.Partial("Nop.Plugin.Something.Something.Views.Plugin.VIEWNAME", item)
You can create a Custom View Engine and add that to your payment plugin. This so you can add routes to Nopcommerce.
First you create a Custom View Engine
CustomViewEngine.cs
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
PartialViewLocationFormats = new[] { "~/Plugins/Misc.HelloWorld/Views/{0}.cshtml" };
ViewLocationFormats = new[] { "~/Plugins/Misc.HelloWorld/Views/{0}.cshtml" };
}
}
Change paths of the Misc.Helloworld to the path of your plugin.
Change the .Edit to the action in your controller.
Change the other information of the controller and the action to the names in your plugin.
After that, create a RouteProvider.cs
ViewEngines.Engines.Insert(0, new CustomViewEngine());
var route = routes.MapRoute("Plugin.Misc.HelloWorld.Edit",
new { controller = "HelloWorld", action = "Edit", },
new { },
new[] { "Nop.Plugin.Misc.HelloWorld.Controllers" }
);
routes.Remove(route);
routes.Insert(0, route);
Change Misc.HelloWorld to the path of your Payment.Plugin,
After that u can add the following to the embedded source
#Html.Partial("Actionname", item)
For more information, see the blog of Alex Wolf

Show AlertDialog from ViewModel using MvvmCross

I am using MvvmCross for creation my Android-app and I faced with the following problem:
When I'm trying to show AlertDialog, that was created in ViewModel, the
"Unhandled Exception: Android.Views.WindowManagerBadTokenException" appears.
public class MyViewModel : MvxViewModel
{
public ICommand ShowAlertCommand { get; private set; }
public AuthorizationViewModel()
{
ShowAlertCommand = new MvxCommand(() =>
{
var adb = new AlertDialog.Builder(Application.Context);
adb.SetTitle("Title here");
adb.SetMessage("Message here");
adb.SetIcon(Resource.Drawable.Icon);
adb.SetPositiveButton("OK", (sender, args) => { /* some logic */});
adb.SetNegativeButton("Cancel", (sender, args) => { /* close alertDialog */});
adb.Create().Show();
});
}
}
When I was researching I have found that it happens because of transmission of the reference to the Context but not on the Activity in the AlertDialog.Builder.
In this topic I found the following decision:
Receive references to the current Activity through the use of GetService(), but I didn't found mvvmcross plugins for work with IMvxServiceConsumer, IMvxAndroidCurrentTopActivity interfaces.
My question is can I show AlertDialog from ViewModel? And how can I get the reference to Activity, but not to the Application.Context?
And what is the correct way to close AlertDialog that the user would stay on the current View?
In general, you should try not to put this type of code into ViewModels
because ViewModels should stay platform independent
because ViewModels should be unit testable - and it's hard to unit test when the code shows a dialog
I'd also recommend you don't put code like this inside a ViewModel Constructor - these constructors are generally called during navigations and displaying a Dialog during a transition is likely to be problematic.
With those things said, if you do want to get hold of the current top Activity within any code, then you can do this using the IMvxAndroidCurrentTopActivity
public interface IMvxAndroidCurrentTopActivity
{
Activity Activity { get; }
}
Using this, any code can get the current Activity using:
var top = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
var act = top.Activity;
if (act == null)
{
// this can happen during transitions
// - you need to be sure that this won't happen for your code
throw new MvxException("Cannot get current top activity");
}
var dlg = new AlertDialog.Builder(act);
//...
dlg.Create().Show();
The use of IMvxAndroidCurrentTopActivity is discussed in MvvmCross: How to pass Android context down to MvxCommand?
The approach taken in that question/answer is also one of the ways I would generally approach showing dialogs from a ViewModel:
I would create an IFooDialog interface
Ideally I would probably make this interface asynchronous - e.g. using async or using an Action<DialogResult> callback parameter
on each platform I would implement that in the UI project
the ViewModels can then use IFooDialog when a dialog is needed and each platform can respond with an appropriate UI action
This 'Dialog Service' type of approach is common in Mvvm - e.g. see articles like http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern (although that article is very Windows specific!)
There are also a few other questions on here about MvvmCross and dialogs - although they may contain reference to older v1 or vNext code - e.g. Alerts or Popups in MvvmCross and Unable run ProgressDialog - BadTokenException while showind

Dynamically adding a custom view to RemoteViews

Could any help me to do this? My code is like:
public CustomClass extends View {
//uses ondraw() to do something
}
For displaying my custom view on the home screen I created a class to extend Broadcast Receiver:
public class customAppWidgetProvider extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.main);
//Here I want to create my custom view class object and I want to add this view to linear layout in main.xml
CustomClass object = new CustomClass(context) ;
LinearLayout layout = new LinearLayout(context) ;
layout.setLayoutParameters(new LayoutParameters(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
layout.addView(object);
views.addview(R.id.linearlayout, (ViewGroup) layout) ;
views.setOnClickPendingIntent(R.id.analog_appwidget,
PendingIntent.getActivity(context, 0,
new Intent(context, AlarmClock.class),
PendingIntent.FLAG_CANCEL_CURRENT));
int[] appWidgetIds = intent.getIntArrayExtra(
AppWidgetManager.EXTRA_APPWIDGET_IDS);
AppWidgetManager gm = AppWidgetManager.getInstance(context);
gm.updateAppWidget(appWidgetIds, views);
}
}
}
But adding ViewGroup to RemoteView reference is not working... The main.xml layout above contains only LinearLayout. I want to add a custom view object to it. But after running this nothing shows on screen...
Please help me to do this. Thanks in Advance.
It's not possible to add a custom View to an app widget. See the "Creating the App Widget Layout" section of the App Widgets Dev Guide for what View types are allowed.
Android 3.0 adds support for some views to display collections. See the "Using App Widgets with Collections" section for details.
Otherwise, to dynamically add an allowed View to an App Widget, after inflating the RemoteViews and getting a reference to it, you can use its addView(View) method, or the addView(View) method on any of the View objects already in the RemoteViews.