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
Related
Im new to xamarin/c#, im trying to make an application with login , and Im trying to pass the logged in userid inside the application, the question is , how do I pass or make the user id keeps floating inside after the login page? Should I keep passing it in every page using queryproperty or there's better way to keep it , like a specific file to to put it so that every page can call it?
You can use the Application.Properties collection to store things that need to be accessible to the entire application.
To store the user ID you would use
Application.Current.Properties("UserID") = UserID;
and to retrieve it you would use
UserID = Application.Current.Properties("UserID");
In C# it's not possible to define true global variables (meaning that they don't belong to any class). using a static class is a valid alternative option so you can create something like this:
public static class Globals
{
public Dictionary<int, UserObject> Users = new Dictionary<int, UserObject>();
}
Now, you'll be able to access The Users's dictionary property and add/remove/modify login users
Following Hans Kesting comment, Please note that An Xamarin app servers a single user at at time, so you can refactor the above from a dictionary to UserObject
Static classes - bad practic for contains info. You can use IoC-container. I don't know xamarin, if you have startup-class (how WPF), you can make ioc-container:
Install Autofac Install-Package Autofac -Version 5.0.0
Rigster user content:
class StartupClass
{
public static IContainer Container { get; set; }
public void OnStartup()
{
var containerBuilder = new ContainerBuilder();
var userContent = smthMethodForGiveUserContent();
containerBuilder.RegisterInstance<User>(userContent); //register UserType!
Container = containerBuilder.Build();
}
}
Resolve user content:
class SmthClass
{
public void Method()
{
using(var scope = StartupClass.Container.BeginLifetimeScope())
{
var userContent = scope.Resolve<User>(); //IT'S YOUR OBJECT, USE!!
//smth code..
}
}
}
The quickest way is to define a variable in App, it can be accessible to the entire project .
Because App itself has been defined inside Application class ,and it is a static property .
public partial class App : Xamarin.Forms.Application
{
public string UserID;
}
// set or get
(App.Current as App).UserID
I'm starting to learn MVVM in C# with mvvmlight. From an other project, I have a given (but self-made) REST-API as a dll. This dll I'm trying to use in this new project. It is completly based on this API. I'm unsure about the "model"-part and don't want to repeat myself while coding.
Problem is like this: The application has several "plugins/sections". Let's look at a sample like the "groups" section. Now the API defines a groups-Entity-Class with all properties like "groupname, permissions, members". Then, the Application must have a groups model and also a groupsViewModel and a GroupsView for the UI. There, I also must list/show "groupname, permissions, members".
Question is: Do I have to redeclare all the properties from the dll-entity-class in the mvvm-model-class? Or what is the best way to use a rest api? Is there any pattern. I read about the Extension Object Pattern. But I have no clue.
Hint: Since the API is written by myself (only classes, no mvvm or other frameworks), I could modify it as needed.
Thanks in advance!
Take a look at the Repository Pattern: https://msdn.microsoft.com/en-us/library/ff649690.aspx. Basically you create a repository which does the API calls, and transforms the entities to a format you can use in your view.
If the objects from the API are already sufficient you can simply return them, otherwise convert them in the Repository to something more useful.
In the ViewModel you then simply ask the repository to return the model, and need not to care how and in what format it got them.
Small Example:
interface IApiRepository
{
Task<ObservableCollection<ApiModel>> GetApiModelsAsync();
}
class ApiRepository : IApiRepository
{
private async Task<ObservableCollection> GetApiModelsAsync()
{
var myCollection = new ObservableCollection<ApiModel>();
var result = await DoMyApiCall();
foreach (result as item)
{
var newModel = new ApiModel();
newModel.fillFromApi(item);
myCollection.Add(newModel);
}
return myCollection;
}
}
class MyViewModel : ViewModelBase
{
private readonly IApiRepository _apiRepository;
public MyViewModel(IApiRepository apiRepository)
{
_apiRepository = apiRepository;
InitializeViewModel();
}
private ObservableCollection<ApiModel> _apiModels;
public ObservableCollection<ApiModel> ApiModels
{
get { return _apiModels; }
set { Set(ref _apiModels, value); }
}
private async void InitializeViewModel()
{
//as soon as the repo is finished ApiModels will raise the RaisePropertyChanged event
ApiModels = await _apiRepository.GetApiModelsAsync();
}
}
//in you ViewModelLocator
SimpleIoC.Default.Register<IApiRepository, ApiRepository>();
SimpleIoC.Default.Register<MyViewModel>();
//construct your viewmodel (with the injected repository)
var vm = SimpleIoc.Default.GetInstance<MyViewModel>();
with the help of this pattern you can do some other awesome things:
if you add a new data source you need only to change the repository and not the ViewModel.
if you want to test your view (for example in Design Mode) you can inject a MockApiRepository which returns sample data instead of doing an API call
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
I am wondering if anyone out there knows how I could create how could i use something like AutoFac to let me dynamically allow dll's to create there own forms and menu items to call them at run time.
So if I have an,
Employee.dll
New Starter Form
Certificate Form
Supplier.dll
Supplier Detail From
Products Form
In my winform app it would create a menu with this and when each one clicked load the relavent form up
People
New Starter
Certificate
Supplier
Supplier Details
Products
So I can add a new class library to the project and it would just add it to menu when it loads up.
Hope that make sense and someone can help me out.
Cheers
Aidan
The first things you have to do is to make your core application extensible. Let's see a simple example.
You will have to allow your external assembly to create item entry in your main app. To do this you can create a IMenuBuilder interface in your main app.
public interface IMenuBuilder
{
void BuildMenu(IMenuContainer container);
}
This interface will allow external assembly to use a IMenuContainer to create MenuEntry. This interface can be defined like this :
public interface IMenuContainer
{
MenuStrip Menu { get; }
}
In your main form, you will have to implement IMenuContainer and call all the IMenuBuilder interface to allow them to create menu entry.
public partial class MainForm : Form, IMenuContainer
{
public MenuStrip Menu
{
get
{
return this.mnsMainApp;
}
}
private void MainForm_Load(Object sender, EventArgs e)
{
ILifetimeScope scope = ... // get the Autofac scope
foreach(IMenuBuilder menuBuilder in scope.Resolve<IEnumerable<IMenuBuilder>())
{
menuBuilder.BuildMenu(this);
}
}
}
In each external assembly, you will have to implement as much IMenuBuilder as needed and one Autofac Module. In this module you will register those IMenuBuilder.
public class XXXModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<XXXMenuBuilder>()
.As<IMenuBuilder>();
}
}
Finally, in your core app, you will have to register all your modules provided by external assembly :
ContainerBuilder builder = new ContainerBuilder();
String path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
IEnumerable<Assembly> assemblies = Directory.GetFiles(path, "*.dll")
.Select(Assembly.LoadFrom);
builder.RegisterAssemblyModules(assemblies);
I'm trying to create a Portable Class such that I can use that across the platforms. It is working fine in Windows Phone 8.1 App. But when it comes to Android, then it is showing the Viewmodel as null and DataContext as Null in debugger which breaks the application debugger. When I create another viewmodel and view to test the app, its working fine on android too. What can be the possible reasons.
EDIT : It is crashing due to the constructor , in which I am passing the business Logic instance. So , Constructor is necessary i think but in that case it is crashing.I am not trying to resolve the ViewModel , i am trying to resolve the Service instance in ViewModel and for the purpose of MVVM, I am keeping the Service out from Droid Project so base.OnCreate(bundle) does not come into scene anyways.
public BookViewModel(ILogic _logic)
{
logic = _logic;
//var ss= Mvx.Resolve<ILogic>();
//var x = Mvx.CanResolve<ILogic>();
_details = logic.Read();
}
Below is the Logic Code :
public class Logic : ILogic
{
#region Attributes
List<Detail.Detail> _details = new List<Detail.Detail>();
DataLayer.DataLayer dl = new DataLayer.DataLayer();
#endregion
#region .ctor
public Logic()
{
populateList();
}
#endregion
#region Methods
private void populateList()
{
_details = dl.Access();
}
Below is the App.cs in ViewModel in which CanResolve is giving False
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
#region Methods
public override void Initialize()
{
Mvx.RegisterType<ILogic, Logic>();
var ss = Mvx.CanResolve<ILogic>();
RegisterAppStart<ViewModels.BookViewModel>();
}
#endregion
}
There are a few questions and answers around similar to this - e.g. similar to MVVMCross ViewModel construction failure notifications
The basic answer is that MvvmCross cannot resolve the ViewModel during the constructor - you have to wait until after the base.OnCreate(bundle) call - at this point the ViewModel will be resolved.
There's also a bit more about when ViewModel's are located in Who should create view model instances in MvvmCross and CoreDispatcher.HasThreadAccess "breaking change" (and probably a few other places too)