I created an app with Windows Template Studio on Visual Studio 2017.
The app is mainly a NavigationDrawer with different pages.
Everything was ok, until I wanted to add a login page.
So I created the XAML of the login page, etc. But now I want it to show before the NavigationDrawer page on app startup.
I seeked some documentation about the App.xaml.cs to know what to change to do that but, because of the use of Windows Template Studio, the code is not really vanilla anymore.
I tried a few things and the only thing I'm able to do right now is to change the shell page of the NavigationDrawer to my Login page.
That's not exactly what I want because my first intention was to make the app unavailable until you log in, and because the NavigationDrawer is still usable the user can still do what he wants to.
My app.xaml.cs looks like this :
using System;
using BasePosteMobilite.Services;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
namespace BasePosteMobilite
{
public sealed partial class App : Application
{
private Lazy<ActivationService> _activationService;
private ActivationService ActivationService
{
get { return _activationService.Value; }
}
public App()
{
InitializeComponent();
// Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy<T> class.
_activationService = new Lazy<ActivationService>(CreateActivationService);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (!args.PrelaunchActivated)
{
await ActivationService.ActivateAsync(args);
}
}
protected override async void OnActivated(IActivatedEventArgs args)
{
await ActivationService.ActivateAsync(args);
}
private ActivationService CreateActivationService()
{
return new ActivationService(this, typeof(ViewModels.LoginViewModel), new Lazy<UIElement>(CreateShell));
}
private UIElement CreateShell()
{
return new Views.ShellPage();
}
}
}
ShellPage.xaml.cs :
using System;
using BasePosteMobilite.ViewModels;
using Windows.UI.Xaml.Controls;
namespace BasePosteMobilite.Views
{
// TODO WTS: Change the icons and titles for all NavigationViewItems in ShellPage.xaml.
public sealed partial class ShellPage : Page
{
private ShellViewModel ViewModel
{
get { return ViewModelLocator.Current.ShellViewModel; }
}
public ShellPage()
{
InitializeComponent();
DataContext = ViewModel;
ViewModel.Initialize(shellFrame, navigationView, KeyboardAccelerators);
}
}
}
ViewModel.Initialize :
public void Initialize(Frame frame, WinUI.NavigationView navigationView, IList<KeyboardAccelerator> keyboardAccelerators)
{
_navigationView = navigationView;
_keyboardAccelerators = keyboardAccelerators;
NavigationService.Frame = frame;
NavigationService.NavigationFailed += Frame_NavigationFailed;
NavigationService.Navigated += Frame_Navigated;
_navigationView.BackRequested += OnBackRequested;
}
You can create a project with login required feature and you will see the following code from ActivateAsync method:
var silentLoginSuccess = await IdentityService.AcquireTokenSilentAsync();
if (!silentLoginSuccess || !IdentityService.IsAuthorized())
{
await RedirectLoginPageAsync();
}
That's it. If you want to redirect to your own page, write the detectation code under ActivationService.ActivateAsync(args) method. If you see the customer is not logged in. Call redirect login method. Here is the code from template studio about redirectlogin:
public async Task RedirectLoginPageAsync()
{
var frame = new Frame();
NavigationService.Frame = frame;
Window.Current.Content = frame;
await ThemeSelectorService.SetRequestedThemeAsync();
NavigationService.Navigate<Views.LogInPage>();
}
Related
I want to make a container-app for my web application, and I decided to do so in Xamarin because the rest of the project is also .NET.
Initially I downloaded and setup the project from Xamarin Sample Pages:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/webview?tabs=windows
I simply changed a few variables in WebPage.cs: https://github.com/xamarin/xamarin-forms-samples/blob/master/WorkingWithWebview/WorkingWithWebview/WebPage.cs
using Xamarin.Forms;
namespace WorkingWithWebview
{
public class WebPage : ContentPage
{
public WebPage()
{
var browser = new WebView();
browser.Source = "https://xamarin.swappsdev.net";
Content = browser;
}
}
}
Secondly I updated App.cs to suit my needs: https://github.com/xamarin/xamarin-forms-samples/blob/master/WorkingWithWebview/WorkingWithWebview/App.
using Xamarin.Forms;
namespace WorkingWithWebview
{
public class App : Application
{
public App ()
{
MainPage = new WebPage();
}
}
}
And boom! I had an app.
Then came the real struggle. In the web application I can – when opening the site (https://xamarin.swappsdev.net) in the browser – click on a button which requests permissions from the device and then display the camera feed in the same window.
When doing the same action in the app nothing happens.
I then started googling for an answer and really didn’t find a lot. And the answers I found seems to be of an older version of Xamarin (?), since I wasn’t able to compare the files and structure in the answer compared to the one of the Xamarin Sample Page.
https://stackoverflow.com/a/50560855
I tried implementing the answer from Robbit here. After a long struggle I managed to compile it and install it on my device but it doesn't actually ask for permissions.
I am at a loss and could need some help/guidance.
Updated:
In my previous answer, it shows how to add camera permission on webview.
The link you provided, it works now. https://xamarin.swappsdev.net/ It seems to provide a camera preview function. It need to check permissions on API 23+.
On Xamarin.Forms, you could use Permissions Plugin. https://github.com/jamesmontemagno/PermissionsPlugin
First, add the camera permission in Android Manifest.
Your Project.Android> Properties> Android Manifest> Required permissions> Camera. After that, it would generate the user permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.CAMERA" />
Create a Utils.cs.
public static class Utils
{
public static async Task<PermissionStatus> CheckPermissions(Permission permission)
{
var permissionStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(permission);
bool request = false;
if (permissionStatus == PermissionStatus.Denied)
{
if (Device.RuntimePlatform == Device.iOS)
{
var title = $"{permission} Permission";
var question = $"To use this plugin the {permission} permission is required. Please go into Settings and turn on {permission} for the app.";
var positive = "Settings";
var negative = "Maybe Later";
var task = Application.Current?.MainPage?.DisplayAlert(title, question, positive, negative);
if (task == null)
return permissionStatus;
var result = await task;
if (result)
{
CrossPermissions.Current.OpenAppSettings();
}
return permissionStatus;
}
request = true;
}
if (request || permissionStatus != PermissionStatus.Granted)
{
var newStatus = await CrossPermissions.Current.RequestPermissionsAsync(permission);
if (!newStatus.ContainsKey(permission))
{
return permissionStatus;
}
permissionStatus = newStatus[permission];
if (newStatus[permission] != PermissionStatus.Granted)
{
permissionStatus = newStatus[permission];
var title = $"{permission} Permission";
var question = $"To use the plugin the {permission} permission is required.";
var positive = "Settings";
var negative = "Maybe Later";
var task = Application.Current?.MainPage?.DisplayAlert(title, question, positive, negative);
if (task == null)
return permissionStatus;
var result = await task;
if (result)
{
CrossPermissions.Current.OpenAppSettings();
}
return permissionStatus;
}
}
return permissionStatus;
}
}
In MainActivity.cs, add the code in OnCreate method.
Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this, savedInstanceState);
OnRequestPermissionsResult is needed in MainActivity.cs.
public override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
And then impletement it.
private async void _button_Clicked(object sender, EventArgs e)
{
webView.Source = "https://xamarin.swappsdev.net/";//"https://test.webrtc.org/";
var status = PermissionStatus.Unknown;
status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
if (status != PermissionStatus.Granted)
{
status = await Utils.CheckPermissions(Permission.Camera);
}
}
I have upload on my GitHub. Check the folder. Test/CameraRuntimePermission_WebView/RuntimePermission
https://github.com/WendyZang/Test.git
Edit:
If you do not want to call this in button click event, you could delete the button in MainPage.xaml.
MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
webView.Source = "https://xamarin.swappsdev.net/";
}
protected override void OnAppearing()
{
base.OnAppearing();
RunTimePermission();
}
public async void RunTimePermission()
{
var status = PermissionStatus.Unknown;
status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
if (status != PermissionStatus.Granted)
{
status = await Utils.CheckPermissions(Permission.Camera);
}
}
After following several guides to enable the WebView's camera, I have finally succeeded.
I followed all of the above steps, and despite getting prompts for allowing the camera I could never get rid of the permissions error (even when debug showed the permissions were granted). I then replaced the MyWebViewRenderer code (as given above by Wendy) with the following (as given on https://forums.xamarin.com/discussion/183988/how-to-give-camera-and-microphone-permission-to-webview-with-xamarin-forms-on-android):
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace App19F_8.Droid
{
public class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.SetWebChromeClient(new MyWebChromeClient(MainActivity.Instance));
Control.Settings.JavaScriptEnabled = true;
}
}
}
public class MyWebChromeClient : WebChromeClient
{
Activity mActivity = null;
public MyWebChromeClient(Activity activity)
{
mActivity = activity;
}
public override void OnPermissionRequest(PermissionRequest request)
{
mActivity.RunOnUiThread(() => {
request.Grant(request.GetResources());
});
}
}
}
In case the link above becomes invalid, I should mention that in Android's MainActivity you should insert the following:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
...
base.OnCreate(savedInstanceState);
Instance = this;
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
Now finally the camera is working :-) I'm not exactly sure what the culprit was in the previous code. Thanks a lot to Wendy, Yelinzh and all for the thorough responses.
I tried implementing the answer from Robbit here. After a long struggle I managed to compile it and install it on my device but it doesn't actually ask for permissions.
I try the code provided by Robbit from the link.
Camera on Xamarin WebView
Actually, it works well. For better understanding, you could use the link below to check. https://test.webrtc.org/
If I use the webview to load the url directly, it would throw the error like below.
If I use the custom renderer from Robbit, it would ask for permission with the code in MyWebViewRenderer.
public override void OnPermissionRequest(PermissionRequest request)
{
mContext.RunOnUiThread(() =>
{
request.Grant(request.GetResources());
});
}
I have also check the link you used, nothing happened. The link would not open the camera in Android device.
Usage of the code from Robbit.
Create the MyWebView in your project.
public class MyWebView : WebView
{
}
Create the MyWebViewRenderer.cs in Android part of your project.
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace WebViewDemo.Droid
{
public class MyWebViewRenderer : WebViewRenderer
{
Activity mContext;
public MyWebViewRenderer(Context context) : base(context)
{
this.mContext = context as Activity;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.Settings.JavaScriptEnabled = true;
Control.ClearCache(true);
Control.SetWebChromeClient(new MyWebClient(mContext));
}
public class MyWebClient : WebChromeClient
{
Activity mContext;
public MyWebClient(Activity context)
{
this.mContext = context;
}
[TargetApi(Value = 21)]
public override void OnPermissionRequest(PermissionRequest request)
{
mContext.RunOnUiThread(() =>
{
request.Grant(request.GetResources());
});
}
}
}
}
Usage:
MainPage.xaml
<StackLayout>
<Button x:Name="_button" Clicked="_button_Clicked" />
<local:MyWebView
x:Name="webView"
HeightRequest="500"
WidthRequest="500" />
</StackLayout>
MainPage.xaml.cs
private void _button_Clicked(object sender, EventArgs e)
{
webView.Source = "https://test.webrtc.org/";//"https://xamarin.swappsdev.net/";
}
You could download the whole project from GitHub folder CameraRuntimePermission_WebView. The Page1 is used to test the link with webview. The MainPage is used to test with custom renderer.
https://github.com/WendyZang/Test.git
I am actually trying to archive global variables in Xamarin where any page can consume it. After a lot of research, looks like the best way to archive such thing is using the Singleton design pattern. I am facing difficulty to implement this. take a look...
global.cs
using System;
namespace xamarin_forms
{
sealed class Global
{
public string test { get; set; }
private static Global _instance = null;
private Global()
{
}
static internal Global Instance()
{
if (_instance == null)
{
_instance = new Global();
}
return _instance;
}
}
}
App.xaml.cs
using Xamarin.Forms;
namespace xamarin_forms
{
public partial class App : Application
{
Global global = Global.Instance();
public App()
{
InitializeComponent();
MainPage = new PageWelcome();
global.test = "123";
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
Ok, so far, I just created my singleton class with a simple test property. I set this to 123 when I initialize my application.
Now, on another page, welcome page...I'd like to read the value that I set previously on the initialization...
PageWelcome.xaml.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace xamarin_forms
{
public partial class PageWelcome : ContentPage
{
public PageWelcome()
{
InitializeComponent();
Global global = Global.Instance();
DisplayAlert("Alert", global.test, "OK");
}
}
}
Actually this is not working. It's returns me a null. So, how to use this correctly ? Thanks !
In your App's constructor, you first create an instance of PageWelcome. This instance reads the test property of your Global singleton and displays its contents in an alert. At this point, no value has been assigned to that property as far as I can see.
It is only after the PageWelcome constructor finishes that you actually assign a value to the test property of your singleton. Change your App constructor to
public App()
{
InitializeComponent();
global.test = "123";
MainPage = new PageWelcome();
}
and it should work as expected.
You don't need a Singleton.
Just create a static class with your variables static and you would be able to use them on any Page, like you want global variables.
// 1. Create static class Global with string _Test
public static class Global
{
public static void Init()
{
// your init class
}
private static string _Test { get; set; }
public static string Test
{
get => return _Test;
set => _Test = value;
}
}
// 2. Init Global in your App.cs
public App()
{
Global.Init();
}
// 3. Then use them on any page
public PageWelcome()
{
Global.Test = "123";
}
I am developing an portable App for Android and iOS. My current function is taking a Screenshot and use that image in the code. Therefor I have an Interface in the portable library.
public interface IFileSystemService
{
string GetAppDataFolder();
}
I am taking the Screenshot also in the portable Library with the following code:
static public bool TakeScreenshot()
{
try
{
byte[] ScreenshotBytes = DependencyService.Get<Interface.IScreenshotManager>().TakeScreenshot();
return true;
}
catch (Exception ex)
{
}
return false;
}
This either calls the Android or the iOS version.
Android:
class ScreenshotManagerAndroid : IScreenshotManager
{
public static Activity Activity { get; set; }
public byte[] TakeScreenshot()
{
if (Activity == null)
{
throw new Exception("You have to set ScreenshotManager.Activity in your Android project");
}
var view = Activity.Window.DecorView;
view.DrawingCacheEnabled = true;
Bitmap bitmap = view.GetDrawingCache(true);
byte[] bitmapData;
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}
The question now is to get the current Activity from my app.
A better way would be to use the Standalone Current Activity Plugin or the Current Activity Property in the Xamarin Essentials Plugin. Then you could just do:
Standalone: CrossCurrentActivity.Current.Activity
Xamarin Essentials: Platform.CurrentActivity
If you do not want to use a plugin and you only have 1 Activity in your app, you could get away with assigning a static variable in MainActivity and referencing that where ever you needed it like this:
public class MainActivity : FormsApplicationActivity {
public static Context Context;
public MainActivity () {
Context = this;
}
}
If you needed Context within a custom renderer, you would want to use the Context passed into the constructor, like this:
public class MyEntryRenderer : EntryRenderer {
private readonly Context _context;
public MyEntryRenderer(Context context) : base(context) {
_context = context;
}
// Now use _context or ((Activity)_context) any where you need to (just make sure you pass it into the base constructor)
}
The old deprecated way would be Context view = (Activity)Xamarin.Forms.Forms.Context
Xamarin automatically assigns the Activity to Forms.Context.
Since the release of Xamarin 2.5, Xamarin.Forms.Forms.Context is obsolete. The Context can now be obtained as follows:
var currentContext = Android.App.Application.Context;
var activity = (Activity)Forms.Context;
or if you are using MainActivity
var activity = (MainActivity)Forms.Context;
If you are using Xamarin Essentials 1.5 or higher, then you can use Platform.CurrentActivity. This is basically the equivalent of using the CurrentActivity plugin.
Ensure you initialise this correctly as per the instructions ie. in MainActivity OnCreate add the following line
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
I was trying to do something similar in Xamarin 5 and am having some luck in both my Android and iOS versions using
Shell.Current.CurrentPage
So something happens like a screenshot or a login and that method (whatever it is) can fire a static event so that any interested activity can look for itself whether it's the active view or not, and if so consume the data (byte array etc.) transported by the event.
class FileChooserPage : ContentPage
{
public FileChooserPage()
{
InitializeComponent();
GoogleDriveService.Authenticated += GoogleDriveService_Authenticated;
}
private void GoogleDriveService_Authenticated(object sender, EventArgs e)
{
if (ReferenceEquals(this, Shell.Current.CurrentPage))
{
Populate(e);
}
}
}
I am in my first app using Xamarin and I'm finding difficulty in page navigation. Researched in various places and getting success the first time I used the navigation.
But when I added a button the page builder that need to navigate is called but does not open any page. When I click the button I've ever worked (list according to the print below) it goes, but I notice that clicking device Windows Phone hardware button click the first failure ie it should be another page in the stack. I do not know how to solve, because in this second button works perfectly but the first can not solve and not find what may be the problem.
Use the Windows Phone project to test and use the Visual Studio 2015 Community.
Buttons screenshot:
MainViewModel.cs where do the command of setting to draw navigation via click on the buttons:
private readonly INavigationService _navigationService;
public MainViewModel()
{
RescisaoCommand = new Command(NavigateToRescisao);
FGTSCommand = new Command(NavigateToFGTS);
_navigationService = DependencyService.Get<INavigationService>();
}
private async void NavigateToRescisao()
{
await _navigationService.NavigateToRescisao();
}
private async void NavigateToFGTS()
{
await _navigationService.NavigateToFGTS();
}
App.cs Constructor:
public App()
{
DependencyService.Register<IMessageService, MessageService>();
DependencyService.Register<INavigationService, NavigationService>();
// The root page of your application
MainPage = new NavigationPage(new MainView());
}
INavigationService.cs Interface:
public interface INavigationService
{
Task NavigateToRescisao();
Task NavigateToFGTS();
}
NavigationService.cs:
public class NavigationService : INavigationService
{
public async Task NavigateToRescisao()
{
await App.Current.MainPage.Navigation.PushAsync(new RescisaoView());
}
public async Task NavigateToFGTS()
{
await App.Current.MainPage.Navigation.PushAsync(new FGTSView());
}
}
FGTSViewModel.cs constructor (works):
public FGTSViewModel()
{
CalculateFgtsCommand = new Command(x => { ShowResult(); });
_messageService = DependencyService.Get<IMessageService>();
//_navigationService = DependencyService.Get<INavigationService>();
}
RescisaoViewModel.cs constructor (not work):
public RescisaoViewModel()
{
Reasons = new List<string>();
TypesOfNotice = new List<string>();
Reasons.Add("Pedido de demissão");
Reasons.Add("Justa causa");
Reasons.Add("Sem justa causa");
Reasons.Add("Término de contrato de experiência");
TypesOfNotice.Add("Trabalhado");
TypesOfNotice.Add("Indenizado");
//_navigationService = DependencyService.Get<INavigationService>();
}
He managed to solve the problem.I checked my bindings and they were incomplete names.
The problem was that there was an error description to help me, I believe this can serve other people.
In a background agent I create (or update) one of application live tiles and this works as expected.
Problem is that when I click this live tile screen flickers but my app is not "restarted" nor "shown".
What's wrong?
I attach small part of the code, but ask for more is you need.
MAIN PAGE
public partial class MainPage : PhoneApplicationPage
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
public MainPage()
{
InitializeComponent();
// Runs background agent: code is simplified
StartAgent();
}
}
BACKGROUND AGENT
public class TileAgent : ScheduledTaskAgent
{
protected override void OnInvoke(ScheduledTask task)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
Vars.UpdateTiles();
});
NotifyComplete();
}
}
STATIC CLASS
public class Vars
{
private static Uri uri = new Uri(
"/MainPage.xaml?tile",
UriKind.RelativeOrAbsolute);
private static RadExtendedTileData ExtendedData
{
get
{
return new RadExtendedTileData()
{
VisualElement = frontTile,
BackVisualElement = backTile,
};
}
}
public static void UpdateTiles()
{
// I perform some task here
// Then I create/update live tile
Telerik.Windows.Controls.LiveTileHelper.CreateOrUpdateTile(
ExtendedData, uri);
}
}
Try /MainPage.xaml?tile=true instead of /MainPage.xaml?tile...
And move NotifyComplete() into the dispatcher call. Otherwise it will be called before the operation has been completed...