I was wondering if someone could shed some light on how best handle the navigation service in a universal app as I'm confused on how to handle this.
If I create a blank application and just use a basic NavigationService as suggested in tutorial for mvvmlight and universal app, it doesn't handle the windows phone hardware back key and when I click it, it just closes the wp8.1 app.
I found an article that suggest to add the HardwareButtons.BackPressed to the app.cs, but I don't like the idea to be honest. Maybe it's ok? Let me know.
The basic IHavigationService is registered in the SimpleIocand is injected in via the constructor but as mentioned, this does not handle the back key for wp8 apps.
When I create a universal hub app, it doesn't use mvvmlight and it creates a navigationHelper class which contains the necessary code to handle both windows and wp apps but every time it is used, the declaration is done in the code behind of the relevant page rather than in the ViewModel.
Any suggestions on how best handle this?
Thanks.
You can combine the best of both world:
In App.xaml.cs, you subscribe to HardwareButtons.BackPressed event:
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
if(NavigationService.CanGoBack)
{
NavigationService.GoBack();
e.Handled = true;
}
}
Create NavigationService class:
public static class NavigationService
{
public static Dictionary<Pages, Type> PageDictionary = new Dictionary<Pages, Type>();
public static Frame MainFrame;
public static void Configure(Frame frame)
{
PageDictionary.Add(Pages.MainPage, typeof(MainPage));
PageDictionary.Add(Pages.Setting, typeof(SettingPage));
MainFrame = frame;
}
internal static void GoBack()
{
if (MainFrame.CanGoBack)
MainFrame.GoBack();
}
internal static bool CanGoBack
{
get
{
return MainFrame.CanGoBack;
}
}
internal static void NavigateTo(Pages page, object parameter)
{
MainFrame.Navigate(PageDictionary[page], parameter);
}
}
Pages is an enum
In App.xaml.cs, after you initiate rootFrame, config the navigation service:
NavigationService.Configure(rootFrame);
Related
The problem is moving from the ServiceLocator anti-pattern to Dependency Injection. In view of my inexperience, I can't shift the DI principle to the code implemented now.
Summary
The Summary section is optional for read. You may want to comment on something, advise.
The main purpose of the program is the process of merging placeholder fields of specific information. Number of information makes need to have infrastructure around. Such as forms, services, database. I have some experience with this task. I managed to create a similar program based on WinForms. And it even works! But in terms of patterns, maintenance, extensibility, and performance, the code is terrible. It is important to understand that programming is a hobby. It is not the main education or job.
The experience of implementing the described task on WinForms is terrible. I started studying patterns and new platform. The starting point is UWP and MVVM. It should be noted that the binding mechanism is amazing.
The first problem on the way was solved independently. It is related to navigation in the UWP via the NavigationView located in the ShellPage connected with ShellViewModel. Along with creating a NavigationService. It is based on templates from Windows Template Studio.
Since there is a working WinForms program and its anti-pattern orientation, there is time and a desire to do everything correctly.
Now I'm facing an architecture problem. Called ServiceLocator (or ViewModelLocator). I find it in examples from Microsoft, including templates from Windows Template Studio. And in doing so, I fall back into the anti-pattern trap. As stated above, I don't want this again.
And the first thing that comes as a solution is dependency injection. In view of my inexperience, I can't shift the DI principle to the code implemented now.
Current implementation
The start point of app UWP is app.xaml.cs. The whole point is to transfer control to ActivationService. Its task is adding Frame to Window.Current.Content and navigated to default page - MainPage. Microsoft documentation.
The ViewModelLocator is a singleton. The first call to its property will call constructor.
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
// Constructor
private ViewModelLocator(){...}
Using ViewModelLocator with View (Page) is like this, ShellPage:
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
Using ViewModelLocator with ViewModel is similar, ShellViewModel:
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
Moving to DI
ShellViewModel has NavigationService from ViewModelLocator as shown above. How can I go to DI at this point? In fact, the program is small. And now is a good time to get away from anti-patterns.
Code
ViewModelLocator
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
private ViewModelLocator()
{
// Services
SimpleIoc.Default.Register<NavigationService>();
// ViewModels and NavigationService items
Register<ShellViewModel, ShellPage>();
Register<MainViewModel, MainPage>();
Register<SettingsViewModel, SettingsPage>();
}
private void Register<TViewModel, TView>()
where TViewModel : class
where TView : Page
{
SimpleIoc.Default.Register<TViewModel>();
NavigationService.Register<TViewModel, TView>();
}
public ShellViewModel ShellViewModel => SimpleIoc.Default.GetInstance<ShellViewModel>();
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
public SettingsViewModel SettingsViewModel => SimpleIoc.Default.GetInstance<SettingsViewModel>();
public NavigationService NavigationService => SimpleIoc.Default.GetInstance<NavigationService>();
ShellPage : Page
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
public ShellPage()
{
InitializeComponent();
// shellFrame and navigationView from XAML
ViewModel.Initialize(shellFrame, navigationView);
}
ShellViewModel : ViewModelBase
private bool _isBackEnabled;
private NavigationView _navigationView;
private NavigationViewItem _selected;
private ICommand _itemInvokedCommand;
public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand<NavigationViewItemInvokedEventArgs>(OnItemInvoked));
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
public bool IsBackEnabled
{
get => _isBackEnabled;
set => Set(ref _isBackEnabled, value);
}
public NavigationViewItem Selected
{
get => _selected;
set => Set(ref _selected, value);
}
public void Initialize(Frame frame, NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
NavigationService.Frame = frame;
NavigationService.Navigated += Frame_Navigated;
NavigationService.NavigationFailed += Frame_NavigationFailed;
}
private void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
NavigationService.Navigate(typeof(SettingsViewModel));
return;
}
var item = _navigationView.MenuItems.OfType<NavigationViewItem>().First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageKey = GetPageKey(item);
NavigationService.Navigate(pageKey);
}
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
NavigationService.GoBack();
}
private void Frame_Navigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = _navigationView.SettingsItem as NavigationViewItem;
return;
}
Selected = _navigationView.MenuItems
.OfType<NavigationViewItem>()
.FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw e.Exception;
}
private bool IsMenuItemForPageType(NavigationViewItem item, Type sourcePageType)
{
var pageKey = GetPageKey(item);
var navigatedPageKey = NavigationService.GetNameOfRegisteredPage(sourcePageType);
return pageKey == navigatedPageKey;
}
private Type GetPageKey(NavigationViewItem item) => Type.GetType(item.Tag.ToString());
Update 1
Am I wrong about the equality between ServiceLocator and ViewModelLocator?
Called ServiceLocator (or ViewModelLocator)
Essentially, the current task is to connected View and ViewModel. NavigationService is beyond the scope of this task. So shouldn't it be in ViewModelLocator?
As #Maess notes, the biggest challenge you face (right now) is refactoring the static dependencies into constructor injection. For example, your ShellViewModel should have a constructor like:
public ShellViewModel(INavigationService navigation)
Once you have done that, you can then set up a DI framework (like NInject) with all your dependencies (kind of like your SimpleIoC thing), and, ideally, pull one root object from the container (which constructs everything else). Usually that's the main view model of the application.
I've done this successfully on multiple projects, both WPF and UWP, and it works great. Only thing you have to be careful of is when creating view models at runtime (as you often do), do it by injecting a factory.
I have implemented a push notification feature for my Xamarin.Forms UWP app and i am able receive notifications and then pop up a toast. I am using the code below for this.
//This method is triggered when a user taps/clicks the notification
private void Toast_Activated(ToastNotification sender, object args)
{
ToastActivatedEventArgs targs = (ToastActivatedEventArgs)args;
var xml = sender.Content.GetXml();
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xml);
XmlNodeList txts = xDoc.GetElementsByTagName("text");
var sitid = txts[0].InnerText;
var id = sitid.Substring(sitid.LastIndexOf(':') + 1);
Debug.WriteLine("Id:" + id);
}
When a user clicks/taps the notification, I want to open a specific page from my PCL project and pass this id variable as an argument. How can i do this ? Any help would be appreciated.
Use ServiceLocator or whatever other dependency injection framework you have to call your navigation service.
If you want to use the Xamarin Forms built in one see here - http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/
Basically you then define an interface of
public interface INavigationService
{
void NavigateTo(String pageKey);
}
Then you create a new class of
public class NavigationService: INavigationService
{
private NavigationPage _navPage;
public void Initialize(NavigationPage navPage)
{
_navPage = navPage;
}
public void NavigateTo(String pageKey)
{
// Get Page from pageKey
_navPage.PushAsync(page);
}
}
If you want to see how it is done in MVVMLight you can look here: http://www.mvvmlight.net/doc/nav1.cshtml
You can just use the ServiceLocator or other to get the navigation service as needed, whether its in native code or not.
OR
The other way around is that you Dependency Inject another Service type and load up another class from inside Forms. Then you just pass the action through to that, and it can perform the navigation while you are inside Forms.
I've use this tutorial for your purpose, I hope to help you
In the ViewModel, I have Save method where I check isValid property.
If isValid is false, then I want to display an error message.
Since AlertDialog is platform specific, I wonder how do you handle that situation in the ViewModel?
public void Save()
{
if (isValid)
{
OnExit(this, null);
}
else
{
//issue an alert dialog here
}
}
Update
I have used the following plugin and added the following line of code as follows, but it throws an error.
else
{
Mvx.Resolve<IUserInteraction>().Alert("it is not valid");
}
Update 2
Chance.MvvmCross.Plugins.UserInteraction is a namespace but it is used as a type error.
Update 3
I have added Acr.UserDialogs plugin and called as follows, but I have got the same error.
Mvx.Resolve<IUserDialogs>().Alert("it is not valid");
Using ACR User Dialogs is the simplest approach.
In your App.cs (Core/PCL) you will need to register the interface:
public class App : MvxApplication
{
public override void Initialize()
{
// Example Other registrations
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
Mvx.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance);
}
}
Then you can call your alert form your ViewModel.
Mvx.Resolve<IUserDialogs>().Alert("it is not valid");
Note for Android Platform support
Then if you are supporting Android you will need to initialize UserDialog with an instance of the activity context. This will have to be done in each activity that you will be making use of UserDialogs or if you have a shared base activity you can do it there.
[Activity]
public class MainActivity : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_main);
// Initialize Acr UserDialogs
UserDialogs.Init(this);
}
}
Alternatively
You can follow the Mvvmcross document on using platform specific implementations of an interface if you need a more custom modal implementation.
This is how I handle the Alert messages in the viewmodel. Try this.
await App.Current.MainPage.DisplayAlert("Active subscription required", "You do not have an active subscription for Part 2 exams", "OK");
There is an existing MvvmCross plugin called User Interaction that allows displaying alerts and collecting inputs from ViewModels.
From the author BrianChance:
Really simple, easy, beautiful ways to show a message box or to collect user input from your ViewModels
Check it out here and NuGet Link Here.
To install the plugin, make sure you override LoadPlugins in your SetUp Class on iOS and Android (and windows phone) like so:
public override void LoadPlugins(MvvmCross.Platform.Plugins.IMvxPluginManager pluginManager)
{
base.LoadPlugins(pluginManager);
pluginManager.EnsurePluginLoaded<Chance.MvvmCross.Plugins.UserInteraction>();
}
My approach is that i use an event for this scenario. My base class for my view models has a EventHandler OnUserNotification, where the views can kinda subscribe to. The UserNotificationType is just an enum and i let the view kinda decide how it reacts to the situation.
The property:
public EventHandler<UserNotificationType> OnUserNotification { get; set; }
The call:
if (OnUserNotification != null)
{
OnUserNotification.Invoke(this, UserNotificationType.ENetworkError);
}
In the view:
private void onUserNotification(object sender, UserNotificationType userNotificationType)
{
// Do Something like showing a Snackbar, AlertDialog, etc...
}
Of course you can make the eventtype more complex if needed.
Didnt try the plugin which got suggested by wishmaster though, so that might be a smoother implementation?
Use Acr.UserDialogs. There is a great examples on github
You can grab it on nuget
It works well with dependency injection or a static singleton UserDialogs.Instance
In the windows 8.1 universal apps, the suspend/resume modes were handled using the NavigationHelper.cs ans SuspensionManager.cs classes included in the APP template. These classes doesn't seem to be there in the windows 10 UWP apps. Is there a way by which we can handle the suspend/resume states?
There's an interesting framework being developed by the community (but mostly I think Jerry Nixon, Andy Wigley etc.) called Template10. Template10 has a Bootstrapper class with OnSuspending and OnResuming virtual methods that you can override. I am not sure that there's an exact example of doing suspension/resuming yet with Template10, but the idea seems to be to make App.xaml.cs inherit from this Bootstrapper class so you can easily override the methods I mentioned.
sealed partial class App : Common.BootStrapper
{
public App()
{
InitializeComponent();
this.SplashFactory = (e) => null;
}
public override Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
// start the user experience
NavigationService.Navigate(typeof(Views.MainPage), "123");
return Task.FromResult<object>(null);
}
public override Task OnSuspendingAsync(object s, SuspendingEventArgs e)
{
// handle suspending
}
public override void OnResuming(object s, object e)
{
// handle resuming
}
}
The above solution will only work for people who install Template10.
The generic solution is,
paste these lines in the constructor of App.xaml.cs
this.LeavingBackground += App_LeavingBackground;
this.Resuming += App_Resuming;
It will look like this
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
this.LeavingBackground += App_LeavingBackground;
this.Resuming += App_Resuming;
}
These are the methods, although you can press TAB and they will autogenerate.
private void App_LeavingBackground(object sender, LeavingBackgroundEventArgs e)
{
}
private void App_Resuming(object sender, object e)
{
}
The methods LeavingBackground and the one not mentioned here EnteredBackground are newly added to uwp.
Before these methods we would use resuming and suspending to save and restore ui, but now the recommended place to do that work is here.Also these are the last places to perform work before the app is resumed. So the work on these methods should be small ui or other stuff like remaking values which are stale as a long held method here will affect app startup time while resuming.
Source
Windows dev material ,
Windoes dev material 2
Thanks , and have a good day.
What I want to achieve:
I want to initiate audio playback from an mp3 and/or aac HTTP stream in a WP7 application
I want to initiate playback from a specific 'PhoneApplicationPage' instance, but still allow navigation to other pages whilst maintaining playback without any interuption - i.e. I want playback to be 'application-scope'
I want to be able to 'seek' within my media
I playback to continue whilst the phone is locked
What I have tried:
MediaElement:
If the MediaElement is not owned by a page, no sound is produced when Play() is called, despite no exceptions being thrown.
After following 'http://blog.jayway.com/2010/10/04/enable-background-audio-for-multiple-pages-in-windows-phone-7/', playback still resets between page transitions
It also seems like a quite a hacky way of doing things...
Microsoft.Xna.Framework.MediaPlayer:
Works, but "MediaPlayer.PlayPosition" is read-only, and there is no seek method.
See post: 'http://forums.create.msdn.com/forums/t/17318.aspx' - Apparently this is by design due to XBox constraints with Xna (?!)
Microsoft Silverlight Media Framework:
http://smf.codeplex.com/
My favourite option, as it seems very comprehensive
Downloaded 'Silverlight Media Framework 2.3, WP7 specific' assemblies from:
http://smf.codeplex.com/releases/view/57991#DownloadId=190196
I know this is hacky, but to get something working, in the code below, the 'SMFPlayer' is static, and added to each page's layout on navigation.
If the 'SMFPlayer' is not owned by a page, no sound is produced when Play() is called, despite no exceptions being thrown.
Playback still resets between page transitions...
Code:
using System;
using System.Diagnostics;
using Microsoft.Phone.Controls;
using Microsoft.SilverlightMediaFramework.Core;
using Microsoft.SilverlightMediaFramework.Core.Media;
using Microsoft.SilverlightMediaFramework.Plugins.Primitives;
namespace WindowsPhoneApplication1
{
public partial class MainPage : PhoneApplicationPage
{
public static readonly SMFPlayer Player = new SMFPlayer();
static MainPage()
{
Player.VolumeLevel = 1.0f;
Player.Playlist.Add(new PlaylistItem {MediaSource = new Uri("http://smf.vertigo.com/videos/wildlife.wmv", UriKind.Absolute)});
Player.LogLevel = LogLevel.All;
Player.LogEntryReceived += PlayerLogEntryReceived;
}
// Constructor
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
LayoutRoot.Children.Add(Player);
}
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
LayoutRoot.Children.Remove(Player);
}
private static void PlayerLogEntryReceived(object sender, CustomEventArgs<LogEntry> e)
{
Debug.WriteLine(e.Value.Severity + e.Value.Message + e.Value.Type);
}
private void button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.RelativeOrAbsolute));
}
}
}
Does anyone have any idea how I can satisfy my requirements?
Example code?
From an architectural point of view, what I really want is a Media Service which i can send streaming URLs to without caring about which page is currently shown.
I eventually found a simple, but effective solution:
http://blog.reis.se/post/Enable-background-audio-for-multiple-pages-in-Windows-Phone-7-e28093-Take-2.aspx
In App.xaml:
<APPLICATION.RESOURCES>
<MEDIAELEMENT x:key="GlobalMedia"></MEDIAELEMENT>
</APPLICATION.RESOURCES>
In App.xaml.cs:
public static MediaElement GlobalMediaElement
{
get { return Current.Resources["GlobalMedia"] as MediaElement; }
}
In your page:
public partial class MyPage : PhoneApplicationPage
{
MediaElement MEAudio;
public MainPage()
{
InitializeComponent();
MEAudio = App.GlobalMediaElement;
}
private void OnSomeEvent(object sender, RoutedEventArgs e)
{
MEAudio.xxxxx();