I am using Prism for Xamarin Forms and Implemented Modularity in the applications. I have .Droid Project, Default PCL Project which has App.xaml file and other content pages. And I have 4 PCL projects which are created as Modules by implementing IModule. Modules are configured in the App.xaml.cs.
My problem is when app initialized , the login page, home page which are in default PCL project loaded properly. But any content page in Modules are Initializing exactly three times every time I navigated to it using _navigationService.NavigateAsync("") from another page either from the same module or the other module. Content page constructor was called three times along with the corresponding ViewModel constructor, but the OnNavigationCompleted in VM is running only once.
May be because of this issue or some other reason, my Content page in the module is taking few more fractions to load fully. This can be easily noticed while loading. Overall Navigation of Content Page in the module is not so smooth.
Is there any solution for this? I have already tried keeping an empty content
in the module page without any controls and the result is same.
Below are the code snippets of my project.
Configure Module in app.xaml.cs
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
Type module1 = typeof(QNPL.Mobile.Module1);
Type module2 = typeof(QNPL.Mobile.Module2);
Type module3 = typeof(QNPL.Mobile.Module3);
Type module4 = typeof(QNPL.Mobile.Module4);
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module1.Name,
ModuleType = module1,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module2.Name,
ModuleType = module2,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module3.Name,
ModuleType = module3,
InitializationMode = InitializationMode.OnDemand
});
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = module4.Name,
ModuleType = module4,
InitializationMode = InitializationMode.OnDemand
});
}
private async void MenuItemClick(object value)
{
if (value != null)
{
MenuDetail menuItem = (MenuDetail)value;
SelectedMenu = menuItem;
if (!string.IsNullOrEmpty(SelectedMenu.ModuleName))
{
_moduleManager.LoadModule(SelectedMenu.ModuleName);
}
await _navigationService.NavigateAsync(SelectedMenu.URL, navParams);
}
}
Module Page:
using Microsoft.Practices.Unity;
using Prism.Modularity;
using Prism.Unity;
using QNPL.Mobile.Module1.API;
using QNPL.Mobile.Module1.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace QNPL.Mobile.Module1
{
public class Module1 : IModule
{
private readonly IUnityContainer _unityContainer;
public Module1(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
}
public void Initialize()
{
_unityContainer.RegisterTypeForNavigation<Module1HomePage>();
_unityContainer.RegisterPopupNavigationService();
_unityContainer.RegisterType<IApiInterface, ApiInterface>();
}
}
}
Content Page in Module1:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="QNPL.Mobile.Module1.Views.Module1HomePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:prismBehaviors="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
x:Name="Module1HomePage"
Title="Home Page"
prism:ViewModelLocator.AutowireViewModel="True">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>
ContentPage .cs file:
using Xamarin.Forms;
namespace QNPL.Mobile.Module1.Views
{
public partial class Module1HomePage : ContentPage
{
public Module1HomePage()
{
InitializeComponent();
}
}
}
Related
My Master page is defined in a separate ContentPage with it's own ViewModel which handles commands for navigating and swapping out the detail page, but it's not working; navigating to a Detail Page from the Master Page VM causes the Master page to disappear. As I understanding you need to navigate from the MasterDetailPage VM. Is there any way to pass the Master Page's VM to the MasterDetailPage VM, or should I be doing something else?
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
x:Class="NGT.Views.MyMasterDetailPage"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:pages="clr-namespace:NGT.Views"
MasterBehavior="Split">
<MasterDetailPage.Master>
<pages:MyMenuPage/>
</MasterDetailPage.Master>
</MasterDetailPage>
and in the Master's Page ViewModel (not the MasterDetailPage ViewModel):
public class MyMenuPageViewModel : ChildBaseViewModel, INavigationAware
{
public MyMenuPageViewModel (INavigationService navigationService)
{
this.navigationService = navigationService;
this.ChangeDetailPage = new DelegateCommand(this.OpenDetailPage)
}
public async void OpenDetailPage()
{
await this.NavigationService.NavigateAsync("FAQPage", animated: false);
}
}
For now I have created an event for every time I want to swap out my detail page and invoke the event from MyMenuPageVM like so OnPageChanges?.Invoke("Page1"); I then subscribe to the event on my MasterDetailPage VM to handle swapping out the detail page.
On MyMenuPageVM (Master Page):
public delegate void MyEventHandler(string page);
public static event MyEventHandler OnPageChanges;
Subscribe to event on MasterDetailPageVM and handle.
public void OnNavigatedTo(INavigationParameters parameters)
{
MyMenuPageViewModel.OnPageChanges += this.ChangePage;
}
public async void ChangePage(string page)
{
switch (page)
{
case "Page1":
await this.navigationService.NavigateAsync("Page1");
break;
case "Page2":
await this.navigationService.NavigateAsync("Page2");
break;
}
}
Not sure this is the right thing, but it works. :)
I would like to use Rg.Plugins.Popup for Xamarin.Forms but unfortunately I cannot add PopupPage to the project. I am using VIsual Studio 2017. In AddNewItem window there is no PopupPage at all.
I tried to add ContentPage instead like this:
public partial class CustomPopupPage : ContentPage
{
public CustomPopupPage ()
{
InitializeComponent ();
}
}
but whenever i try to change type ContentPage to PopupPage I get following error: Partial declarations of 'CustomPopupPage' must not specify different base classes.
The problem is that second partial class is in auto-generated file CustomPopupPage.xaml.g.cs and I cannot modify that file because each time application is compiling it rewrites that file.
I think I am missing something obvious here because demo is working fine.
PopupPage is a subclass of ContentPage .So you have to add a new ContentPage and change the superclass both in xaml and code benind .
Firstly , install the package Rg.Plugins.Popup from nuget in share project and specific platform (iOS and Android).
The plugin requires to be initialized. To use a PopupPage inside an application, each platform application must initialize the Rg.Plugins.Popup. This initialization step varies from platform to platform and is discussed in the following sections.
iOS ->AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
Android->MainActivity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication (new App ());
}
xaml
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
x:Class="MyProject.MyPopupPage">
<!--You can set an animation in the xaml file or in the csharp code behind-->
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<Label
Text="Test"/>
</StackLayout>
</pages:PopupPage>
in code behind
public partial class MyPopupPage : Rg.Plugins.Popup.Pages.PopupPage
{
public MyPopupPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
}
Update
It seems an existing issue of vs 2017 , on VS 2019 it works fine . And I will post this issue to product teams .
I'm using Xamarin.Forms and Prism to create my mobile app.
I have a screen with 2 entries. When entering the screen, I'd like to set the focus on the first entry.
Then after the user entered data in this entry and validated it, I'd like to set the focus to the second entry.
Based on first answer:
I should do something wrong. I've created a small new Prism project to test it :
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:testEntry"
x:Class="testEntry.Views.MainPage"
Title="{Binding Title}">
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label Text="Welcome to Xamarin Forms and Prism!" />
<local:MyEntry Placeholder="" x:Name="entry1" />
<Button Text="set focus on entry1" Clicked="Button_Clicked"/>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
using Xamarin.Forms;
namespace testEntry.Views
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
entry1.Focus(); //Not Working
}
private void Button_Clicked(object sender, EventArgs e)
{
entry1.Focus(); //Working
}
}
}
MyEntry.cs (in Main project)
using Xamarin.Forms;
namespace testEntry
{
public class MyEntry : Entry
{
}
}
MyEntryRenderer.cs (in Android Project)
using Android.Content;
using Android.Views;
using Android.Views.Accessibility;
using Xamarin.Forms.Platform.Android;
namespace testEntry.Droid
{
public class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
}
}
Unfortunately, still nofocus on my field :'(
Finally, and thanks to Saamer, I found another way of doing it by using EventAggregator.
public class FocusChanged : PubSubEvent<String> { }
Then in my view model :
IEventAggregator _ea;
public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator) : base(navigationService)
{
_ea = eventAggregator;
}
In the viewModel, whenever I want to set the focus to a field, I'm sending an event :
_ea.GetEvent<FocusChanged>().Publish("Source");
And in my view's code behind, I handle this event:
IEventAggregator _ea;
public MainPage(IEventAggregator eventAggregator)
{
InitializeComponent();
_ea = eventAggregator;
_ea.GetEvent<FocusChanged>().Subscribe(SetFocusOnControl); //Name of method which will handle this event
}
/// set the focus on entry based on parameter
/// each event value will set focus on a specific entry (example: source is linked to entry txtScanSrc)
private async void SetFocusOnControl(String fieldName)
{
Entry l_view;
switch(fieldName)
{
case "source": l_view = this.FindByName<Entry>("txtScanSrc"); break;
case "quantity": l_view = this.FindByName<Entry>("txtQty"); break;
case "tote": l_view = this.FindByName<Entry>("txtScanTote"); break;
case "pallet": l_view = this.FindByName<Entry>("txtScanPalout"); break;
case "destination": l_view = this.FindByName<Entry>("txtScanDest"); break;
default: l_view = this.FindByName<Entry>("txtScanSrc"); break;
}
await WaitAndExecute(500, () => { l_view.Focus(); });
}
There's a way of doing this using the Accessibility APIs of each of the platforms. Xamarin forms doesn't have all the platform features of accessibility yet so you d have to create a custom renderer and then call the focus method in a life cycle event of the page.
So calling this Focus function would cause the app to focus on that element. You generally don't want to do it because the app purposefully focuses on what it does so accessible users have a consistent experience. But if you really want to override the default behavior, in Android it's something like this
public static void Focus(View view)
{
view.SendAccessibilityEvent(EventTypes.ViewFocused);
}
And in iOS, you have to use the PostNotification apis which will be some variation of this
UIAccessibility.PostNotification(UIAccessibilityPostNotification.ScreenChanged, entry element)
You can look more into Accessibility Focus to get the exact answer
I have a MainPage in PCL which then navigates to a Platform Specific
Login Page on LoginButton Click Event
The LoginPage is inherited from
platform specific PageRenderer as it needs platform specific
Authentication to Social Providers (Facebook, Google, Twitter,
Microsoft, etc.)
I am using Xamarin.Auth to do the authentication.
Inside the OnElementChanged event of the LoginPage, it instantiates
the OAuth2Authenticator object.
Upon successful instantiation (based
on provider and app details), it needs to call the UI of the specific
provider.
To do that, I call the auth.GetUI where auth is
Xamarin.Auth.OAuth2Authenticator object.
I have two questions:
In UWP, how do I navigate to the provider login UI? More specifically, what is the equivalent in UWP of the following code snippets in iOS and Android? In iOS, the following code is used:
PresentViewController(auth.GetUI(), true, null);
where auth is Xamarin.Auth.OAuth2Authenticator object.
In Android the following is used:
activity.StartActivity(auth.GetUI(activity));
I am looking for the equivalent code in UWP. Please bear in mind that these calls are made from the LoginPage which is derived from Platform specific PageRenderer
How do I navigate back to my MainPage (which is in PCL) upon successful authentication?
The code is based off of a sample from the following source:
http://www.c-sharpcorner.com/article/oauth-login-authenticating-with-identity-provider-in-xamarin-forms/
Here is my code for the LoginPage:
using System;
using Valufy;
using Xamarin.Forms.Platform.UWP;
using System.ComponentModel;
using Valufy.UWP;
using Valufy.AuthConfiguration;
using Xamarin.Forms;
[assembly: ExportRenderer(typeof(ProviderLoginPage), typeof(LoginRenderer))]
namespace Valufy.UWP
{
class LoginRenderer: PageRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
//Get and Assign ProviderName from ProviderLoginPage
ProviderLoginPage loginPage = (ProviderLoginPage)Element;
string providername = loginPage.ProviderName;
//Create OauthProviderSetting class with Oauth Implementation .Refer Step 6
OAuthProviderSetting oauth = new OAuthProviderSetting();
Xamarin.Auth.OAuth2Authenticator auth = oauth.LoginWithProvider(providername);
// After facebook,google and all identity provider login completed
auth.Completed += Auth_Completed;
Type page_type = auth.GetUI();
//////THIS IS WHERE I AM STUCK...HOW DO I GO TO THE PROVIDER AUTH UI ////////////
//this.Frame.Navigate(page_type, auth);
//parentPage.Navigation.PushModalAsync(auth.GetUI());
}
}
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
OAuthConfig.User = new UserDetails();
// Get and Save User Details
OAuthConfig.User.Token = e.Account.Properties["oauth_token"];
OAuthConfig.User.TokenSecret = e.Account.Properties["oauth_token_secret"];
OAuthConfig.User.TwitterId = e.Account.Properties["user_id"];
OAuthConfig.User.ScreenName = e.Account.Properties["screen_name"];
/////NOW, HOW GO I GO BACK TO THE CALLING PAGE IN PCL ///////////////////
}
else
{
// The user cancelled
/////NOW, HOW GO I GO BACK TO THE CALLING PAGE IN PCL ///////////////////
}
}
}
}
Here is the code to navigate to the provider login for UWP:
WindowsPage windowsPage = new WindowsPage();
_frame = windowsPage.Frame;
if (_frame == null)
{
_frame = new Windows.UI.Xaml.Controls.Frame
{
Language = global::Windows.Globalization.ApplicationLanguages.Languages[0]
};
windowsPage.Content = _frame;
SetNativeControl(windowsPage);
}
Type pageType = auth.GetUI();
_frame.Navigate(pageType, auth);
To navigate back to my page upon successful authentication, here is the code:
private async void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri("https://login.microsoftonline.com/common/oauth2/V2.0/token?oauth2_access_token=" + e.Account.Properties["access_token"]), null, e.Account);
try
{
string response = await MSGetUserInfo(e.Account);
}
catch (System.OperationCanceledException)
{
}
catch (Exception ex)
{
}
this.Element.Navigation.PushModalAsync(new MainPage());
}
else
{
// The user cancelled
}
}
1- In UWP, how do I navigate to the provider login UI
You need to create a UWP Page object, the same object you are will use to display in the renderer is the one you will use to do the navigation.
Type page_type = auth.GetUI();
page = new MyUWPLoginPage();
page.Frame.Navigate(page_type, auth);
2- How do I navigate back to my MainPage (which is in PCL) upon successful authentication?
There are many ways to do this, the easiest one is creating a public method in your ProviderLoginPage class and from the renderer classes call this method and pass-in the parameters.
public class ProviderLoginPage: ContentPage
{
......
public void AuthenticationCompleted(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
// Do your logic
}
}
In your Renderer using the Element:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
var xamElement = Element as ProviderLogin;
xamElement?.AuthenticationCompleted(sender, e);
}
Move all the logic to the PCL class so you don't have to repeat it on each renderer.
This should help.
So here's my predicament. I'm writing a custom, one-off content management system, and I can not for the life of me getting this method to work correctly. What I want to do is create a laundry list worth of methods in separate folders and call them as I need them on whichever web forms I want to call them on.
I created a WebApp and created a folder inside of the app called App_Code. Inside of App_Code, there is a public class called "TestimonialService". Here it is:
/******************** TESTIMONIAL SERVICE ****************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BlueTreeSecurity.App_Code.Data;
namespace BlueTreeSecurity.App_Code.Testimonials
{
public class TestimonialService
{
private readonly CMSObjectContext _context;
public TestimonialService(CMSObjectContext context)
{
this._context = context;
}
#region methods
/// <summary>
/// Gets all testimonials
/// </summary>
/// <returns>testimonial collection</returns>
public List<Testimonial> GetAllTestimonials()
{
var query = from t in _context.Testimonials
orderby t.DisplayOrder ascending
select t;
if (query.Count() > 0)
{
var testimonial = query.ToList();
return testimonial;
}
else
{
return null;
}
}
#endregion
}
}
Then on the actual aspx.cs page I call said function like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BlueTreeSecurity.App_Code;
using BlueTreeSecurity.App_Code.Data;
using BlueTreeSecurity.App_Code.Testimonials;
namespace BlueTreeSecurity
{
public partial class Testimonials : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "Testimonials | ...";
Bind_Data();
}
protected void Bind_Data()
{
/** when i try to use intellisense here it's not recognized. **/
var testimonials = TestimonialService.GetAllTestimonials();
rptTestimonials.DataSource = testimonials;
rptTestimonials.DataBind();
}
}
}
The exact error spat back is this:
Error 1
An object reference is required for the non-static field, method, or property
'BlueTreeSecurity.App_Code.Testimonials.TestimonialService.GetAllTestimonials()'
Anything would be appreciated, guys. I'm ripping my hair out here.
Here's the Project structure
- Blue Tree Security (main project)
- App_Code
+ Data
+ Testimonials
+ TestimonialService.cs
Rest of the .aspx, .aspx.cs, and .ascx files.
If i'm not totally misinterpreting here, the error message you are getting is telling you what the problem is. Make GetAllTestimonials() static or instantiate a TestimonialService instance.
protected void Bind_Data()
{
var testimonialService = new TestimonialService(yourContextObect);
var testimonials = testimonialService.GetAllTestimonials();
rptTestimonials.DataSource = testimonials;
rptTestimonials.DataBind();
}