I am trying to handle protocol activation and as per docs I should handle all of that within OnLaunched method so that is what I am trying to do here, but Microsoft.Windows.AppLifecycle.ProtocolActivatedEventArgs doesnt exist.
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var activatedArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
var e = args.UWPLaunchActivatedEventArgs;
InitializeRootFrame(e);
if (activatedArgs.Kind is ExtendedActivationKind.Launch)
{
if (!e.PrelaunchActivated)
{
if (RootFrame.Content == null)
{
RootFrame.Navigate(typeof(LoginPage), e.Arguments);
}
Window.Current.Activate();
}
}
else //Launched by some other means other than normal launching
{
try
{
if (activatedArgs.Kind is ExtendedActivationKind.Protocol && activatedArgs is Microsoft.Windows.AppLifecycle.ProtocolActivatedEventArgs eventArgs)
{
//var a = activatedArgs.Data as ProtocolActivatedEventArgs;
var queryParameters = HttpUtility.ParseQueryString(activatedArgs.Data.Uri.Query);
PocessQueryForToken(queryParameters);
}
}
catch (Exception)
{
}
finally
{
RootFrame.Navigate(typeof(LoginPage));
Window.Current.Activate();
HasLaunched = true;
}
}
HasLaunched = true;
}
There is only a AppActivationArguments Class in the Microsoft.Windows.AppLifecycle NameSpace. So the behavior you got is expected because you are looking for a class that doesn't even exist.
Based on the document for AppActivationArguments, we could know that the activatedArgs we got contains a data object which has one of the following data types, depending on the activation type specified by the Kind property.
File ->IFileActivatedEventArgs
Protocol ->IProtocolActivatedEventArgs
StartupTask ->IStartupTaskActivatedEventArgs
The IProtocolActivatedEventArgs should be the thing that we are looking for. The document here-ProtocolActivatedEventArgs Class shows that this Class comes from the Windows.ApplicationModel.Activation Namespace.
So the code should looks like this:
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var eventargs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
if (eventargs.Kind is ExtendedActivationKind.Protocol && eventargs.Data is Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs)
{
ProtocolActivatedEventArgs ProtocolArgs = eventargs.Data as ProtocolActivatedEventArgs;
var uri = ProtocolArgs.Uri;
}
}
Related
In a WKWebView, clicking on the Tel: links (example: ) does not open the phone dialer with the number from the link as they do in Chrome/Safari.
I have looked at the solution from the link below:
https://forums.xamarin.com/discussion/103689/after-ios-11-upgrade-wkwebview-does-not-load-my-website
However, in my C# project, I am unable to use two base classes (UIViewController, WKNavigationDelegate) in my class as my class WebViewController cannot have multiple base classes.
Is it possible to do this in the DidFinishNavigation method to open the dialer when Tel: links are clicked?
My full code is below with changes that mimics the idea from the link above. Would it be possible for me to achieve this with the way my web view is designed?
[Register("WebViewController")]
public class WebViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
WKWebView webView = new WKWebView(View.Frame, new
WKWebViewConfiguration());
View.AddSubview(webView);
View.SendSubviewToBack(webView);
webView.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
var url = new NSUrl("link goes here");
var request = new NSUrlRequest(url);
webView.LoadRequest(request);
webView.AllowsBackForwardNavigationGestures = true;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
NavigationController.NavigationBarHidden = true;
}
//open email and tel links
// https://forums.xamarin.com/discussion/103689/after-ios-11-upgrade-wkwebview-does-not-load-my-website
//https://forums.xamarin.com/discussion/47335/how-to-call-a-set-phone-number-from-a-button-click-using-xamarin-ios
[Export("webView:didFinishNavigation:")]
//[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
void DidFinishNavigation(WKWebView webView, WKNavigation navigation, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var navType = navigationAction.NavigationType;
var targetFrame = navigationAction.TargetFrame;
var url = navigationAction.Request.Url;
if (
(url.ToString().StartsWith("http") && targetFrame == null)
||
url.ToString().StartsWith("mailto:")
|| url.ToString().StartsWith("tel:")
|| url.ToString().StartsWith("Tel:"))
{
UIApplication.SharedApplication.OpenUrl(url);
}
}
}
}
Fixed it by adding a custom navigation delegate class:
public override void ViewDidLoad()
{
base.ViewDidLoad();
WKWebView webView = new WKWebView(View.Frame, new WKWebViewConfiguration());
View.AddSubview(webView);
View.SendSubviewToBack(webView);
webView.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
var url = new NSUrl("link");
var request = new NSUrlRequest(url);
webView.LoadRequest(request);
webView.AllowsBackForwardNavigationGestures = true;
//assign delegate
webView.NavigationDelegate = new MyWKNavigationDelegate();
}
//custom delegate
class MyWKNavigationDelegate : WKNavigationDelegate
{
[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction
navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
{
var navType = navigationAction.NavigationType;
var targetFrame = navigationAction.TargetFrame;
var url = navigationAction.Request.Url;
if (
url.ToString().StartsWith("http") && (targetFrame != null &&
targetFrame.MainFrame == true)
)
{
decisionHandler(WKNavigationActionPolicy.Allow);
}
else if (
//(url.ToString().StartsWith("http") && targetFrame == null)
//||
url.ToString().StartsWith("mailto:")
|| url.ToString().StartsWith("tel:")
|| url.ToString().StartsWith("Tel:"))
{
//decisionHandler(WKNavigationActionPolicy.Allow);
UIApplication.SharedApplication.OpenUrl(url);
}
}
}
How do I make this method into an event?
BarcodeScannerRenderer.cs:
void IScanSuccessCallback.barcodeDetected(MWResult result)
{
if (result != null)
{
try
{
var scan = Element as BarcodeScannerModal;
if (scan == null)
return;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
And pass the value of result into another class, specifically in here:
(BarcodeScanner.cs)
public async Task<object[]> GetResult()
{
TaskCompletionSource<object[]> tcs = new TaskCompletionSource<object[]>();
scanPage.OnScanResult += async (result) =>
{
object[] scanResult = new object[2];
SharedAppSettings.Sounds.PlayBeep();
scanResult[0] = resultFinal.text;
scanResult[1] = resultFinal.typeText;
await PopupNavigation.PopAsync();
tcs.SetResult(scanResult);
};
return await tcs.Task;
}
If you ever wonder what type of Barcode Scanner I am using, it's Manatee Works Barcode Scanner.
This answer will probably have to be adapted to changes to the question, so do not consider it complete:
To raise an event you'd do something like this:
// Given you have a custom EventArgs class ...
// Define an event on which clients can register their handlers
public event EventHandler<BCDetectedEventArgs> BarcodeDetected;
// infoObject should probably be of the type what `scan` is.
protected virtual void OnBarcodeDetected( object infoObject )
{
// Check if there is at least one handler registered
var handler = BarcodeDetected;
if( handler != null )
{
handler(this, new BCDetectedEventArgs(infoObject));
}
}
void IScanSuccessCallback.barcodeDetected(MWResult result)
{
if (result != null)
{
try
{
var scan = Element as BarcodeScannerModal;
if (scan == null)
return;
else
OnBarcodeDetected( scan );
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
See also for reference https://msdn.microsoft.com/en-us/library/db0etb8x(v=vs.110).aspx
The part in BarcodeScanner.cs is a little more tricky because your snippet suggests a "polling" design. You would first have to adapt to register to the event from above snippet and act on the event in an appropriate handler method.
How I can refresh ParseUser.CurrentUser in Xamarin for iOS ?
There is no any method like refresh().
From the Parse forums:
ParseUser currentUser = ParseUser.getCurrentUser();
currentUser.fetchInBackground(new GetCallback<ParseObject>() {
public void done(ParseObject object, ParseException e) {
if (e == null) {
ParseUser currUser = (ParseUser) object;
// Do Stuff with currUSer
} else {
// Failure!
}
}
});
Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName
I'm maintaining a legacy WebForms application and one of the pages just serves GET requests and works with many query string parameters. This work is done in the code-behind and does a lot of this type of check and casting.
protected override void OnLoad(EventArgs e)
{
string error = string.Empty;
string stringParam = Request.Params["stringParam"];
if (!String.IsNullOrEmpty(stringParam))
{
error = "No parameter";
goto LoadError;
}
Guid? someId = null;
try
{
someId = new Guid(Request.Params["guidParam"]);
}
catch (Exception){}
if (!someId.HasValue)
{
error = "No valid id";
goto LoadError;
}
// parameter checks continue on
LoadError:
log.ErrorFormat("Error loading page: {0}", error);
// display error page
}
I'd like to create a testable class that encapsulates this parsing and validation and moves it out of the code-behind. Can anyone recommend some approaches to this and/or examples?
As a first big step, I'd probably create some form of mapper/translator object, like this:
class SpecificPageRequestMapper
{
public SpecificPageRequest Map(NameValueCollection parameters)
{
var request = new SpecificPageRequest();
string stringParam = parameters["stringParam"];
if (String.IsNullOrEmpty(stringParam))
{
throw new SpecificPageRequestMappingException("No parameter");
}
request.StringParam = stringParam;
// more parameters
...
return request;
}
}
class SpecificPageRequest
{
public string StringParam { get; set; }
// more parameters...
}
Then your OnLoad could look like this:
protected override void OnLoad(EventArgs e)
{
try
{
var requestObject = requestMapper.Map(Request.Params);
stringParam = requestObject.StringParam;
// so on, so forth. Unpack them to the class variables first.
// Eventually, just use the request object everywhere, maybe.
}
catch(SpecificPageRequestMappingException ex)
{
log.ErrorFormat("Error loading page: {0}", ex.Message);
// display error page
}
}
I've omitted the code for the specific exception I created, and assumed you instantiate a mapper somewhere in the page behind.
Testing this new object should be trivial; you set the parameter on the collection passed into Map, then assert that the correct parameter on the request object has the value you expect. You can even test the log messages by checking that it throws exceptions in the right cases.
Assuming that you may have many such pages using such parameter parsing, first create a simple static class having extension methods on NamedValueCollection. For example,
static class Parser
{
public static int? ParseInt(this NamedValueCollection params, string name)
{
var textVal = params[name];
int result = 0;
if (string.IsNullOrEmpty(textVal) || !int.TryParse(textVal, out result))
{
return null;
}
return result;
}
public static bool TryParseInt(this NamedValueCollection params, string name, out int result)
{
result = 0;
var textVal = params[name];
if (string.IsNullOrEmpty(textVal))
return false;
return int.TryParse(textVal, out result);
}
// ...
}
Use it as follows
int someId = -1;
if (!Request.Params.TryParseInt("SomeId", out someId))
{
// error
}
Next step would be writing page specific parser class. For example,
public class MyPageParser
{
public int? SomeId { get; private set; }
/// ...
public IEnumerable<string> Parse(NamedValueCollection params)
{
var errors = new List<string>();
int someId = -1;
if (!params.TryParseInt("SomeId", out someId))
{
errors.Add("Some id not present");
this.SomeId = null;
}
this.SomeId = someId;
// ...
}
}