Object reference crash in ViewWillAppear in PageRenderer xamarin forms ios - xamarin.forms

We customize the toolbar menu for navigation back button in xamarin forms ios. I am getting below crash in appcenter.
ToolbarMenuCustomRenderer.ViewWillAppear (System.Boolean animated)
SIGABRT: Object reference not set to an instance of an object
Code snippet below:
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
CustomToolbarContentPage page = Element as CustomToolbarContentPage;
if (page == null)
return;
#region for soft back button
UIViewController root = NavigationController.TopViewController;
if (!page.NeedOverrideSoftBackButton)
return;
string title = "<" + (string.IsNullOrEmpty(NavigationPage.GetBackButtonTitle(Element)) ? "" : NavigationPage.GetBackButtonTitle(Element));
root.NavigationItem.SetLeftBarButtonItem(
new UIBarButtonItem(title, UIBarButtonItemStyle.Plain, (sender, args) =>
{
page.OnSoftBackButtonPressed();
}), true);
#endregion
}
How to resolve this in xamarin forms ios?

I create a sample app with your code and works fine, you can check the code below to see if you miss something:
NewPage:
namespace My_Forms_test.Views
{
public partial class NewPage2 : ContentPage
{
public NewPage2()
{
InitializeComponent();
}
public void OnSoftBackButtonPressed()
{
Navigation.PopToRootAsync();
}
}
}
CustomRenderer:
[assembly:ExportRenderer(typeof(NewPage2),typeof(MyPageRenderer))]
namespace My_Forms_test.iOS
{
public class MyPageRenderer:PageRenderer
{
public MyPageRenderer()
{
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
NewPage2 page=Element as NewPage2;
UIViewController root = NavigationController.TopViewController;
string title = "<" + (string.IsNullOrEmpty(NavigationPage.GetBackButtonTitle(Element)) ? " " : NavigationPage.GetBackButtonTitle(Element));
root.NavigationItem.SetLeftBarButtonItem(
new UIBarButtonItem(title, UIBarButtonItemStyle.Plain, (sender, args) =>
{
page.OnSoftBackButtonPressed();
Console.WriteLine("This method is trigged"); }), true);
}
}
Here are ScreenShots:
You can also refer to this documents https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/contentpage

Related

Enabling basic authentication in webview without custom WebViewClient in Xamarin Forms

I'm using a webview in my Xamarin Forms project with Hybrid Renderer and webview, because I have to inject javascript code inside the page.
In my main project I have a CustomWebview.cs:
namespace ClotureSiadForms.Renderer
{
public class CustomWebView : WebView
{
public string js = "";
public CustomWebView()
{
Navigating+= WebViewNavigating;
Navigated+=WebViewNavigated;
}
private void WebViewNavigated(object sender, WebNavigatedEventArgs args)
{
EvaluateJavaScriptAsync(js);
}
public void WebViewNavigating(object sender, WebNavigatingEventArgs args)
{
if (args.Url.StartsWith("tel:"))
{
var tel = args.Url.Split(':')[1];
args.Cancel = true;
Xamarin.Essentials.PhoneDialer.Open(tel);
}
else if (!args.Url.StartsWith("http") || args.Url.EndsWith(".apk") || args.Url.EndsWith(".pdf") || args.Url.EndsWith(".zip"))
{
args.Cancel = true;
Xamarin.Essentials.Launcher.OpenAsync(args.Url);
}
}
}
}
In my Android project I have a HybridWebViewRenderer.cs:
[assembly: ExportRenderer(typeof(CustomWebView), typeof(HybridWebViewRenderer))]
namespace ClotureSiadForms.Droid.Renderer
{
internal class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
CustomWebView webview = e.NewElement as CustomWebView;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.SavePassword = true;
}
}
}
}
As is, it worked and was able to download files
But as I needed basic authentication, I added a custom webviewclient inside HybridWebViewRenderer.cs:
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
CustomWebView webview = e.NewElement as CustomWebView;
Control.Settings.JavaScriptEnabled = true;
Control.Settings.DomStorageEnabled = true;
Control.Settings.SavePassword = true;
var login = Preferences.Get("login", "");
var password = Preferences.Get("password", "");
Control.SetWebViewClient(new AuthWebViewClient(login, password));
}
}
public class AuthWebViewClient : WebViewClient
{
private string Username;
private string Password;
public AuthWebViewClient(string username, string password)
{
Username = username;
Password = password;
}
public override void OnReceivedHttpAuthRequest(Android.Webkit.WebView view, HttpAuthHandler handler, string host, string realm)
{
handler.Proceed( Username,Password);
}
}
And authentication works, but WebViewNavigating is now called once, then the custom client is set and then WebViewNavigating is never more called.
Then my question is, can't I use basic auth without a custom client or is there a way to keep using my customwebview with the client ?
And authentication works, but WebViewNavigating is now called once, then the custom client is set and then WebViewNavigating is never more called.
I tested the code you provided and added Breakpoint to WebViewNavigating method. Even if you do not add webviewclient, it will only call WebViewNavigating once.
You can put the code in WebViewNavigating to ShouldInterceptRequest:
public class AuthWebViewClient : WebViewClient
{
...
public override WebResourceResponse ShouldInterceptRequest(Android.Webkit.WebView view, IWebResourceRequest request)
{
var url = request.Url;
...
}
}
Whenever the WebView begins loading a new page, it will call ShouldInterceptRequest.

Allow popups to work in Xamarin Forms WebView

This seems like a simple thing - I want to use a WebView to load / run some JavaScript from a 3rd party.
When I load the page I get a message saying "You must allow popups for this to work". I think I need to create a custom WebView (for iOS and Android!?) and I assume I need one in the shared project too?
I added this to my Android project:
using Android.Content;
using MyApp.Custom;
using MyApp.Droid.Custom;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace MyApp.Droid.Custom
{
public class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.Settings.JavaScriptCanOpenWindowsAutomatically = true;
Control.Settings.JavaScriptEnabled = true;
}
}
}
and this to my shared project
using Xamarin.Forms;
namespace MyKRing.Custom
{
public class CustomWebView : WebView
{
}
}
and then used "CustomWebView" in my XAML.
<custom:CustomWebView x:Name="WebView"
HeightRequest="1000"
Source="{Binding WebViewContent}"
WidthRequest="1000" />
My TestPageViewModel has
public WebViewSource WebViewContent
{
get => _webViewContent;
set => SetProperty(ref _webViewContent, value);
}
public TestPageViewModel()
{
AmountEntryFrameVisible = true;
NuapayFrameVisible = false;
ConfirmLoadCommand = new Command(ExecuteConfirmLoadCommand);
var localHtml = new HtmlWebViewSource();
localHtml.Html = #"<html>
<body>
<p>This is a test</p>
<script src='https://testurl.com/test.js\'></script>
<script>
TestJS.showUI('1234567890', 'https://testurl.com/ui/');
</script>
</body>
</html>";
WebViewContent = localHtml;
}
With the aim of making the JS run when the page is loaded by WebView.
Am I anywhere close? What is wrong with the above, as it just doesn't do anything (that ( can see).
1. Create the CustomWebView and create the ShowDialog method.
public class CustomWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView ),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void ShowDialog(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
2. Consume the CustomWebView
<local:CustomWebView x:Name="customWebView " Uri="index.html" />
3. Register the ShowDiaplg action to be invoked from JavaScript
customWebView.RegisterAction(data => DisplayAlert("Alert", DateTime.Now + "/n" + data, "OK"));
4. Create the custom renderer on Android platform.
CustomWebViewRenderer .cs:
[assembly: ExportRenderer(typeof(CustomWebView ), typeof(CustomWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class CustomWebViewRenderer: WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge2.showDialog(data);}";
Context _context;
public CustomWebViewRenderer (Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((CustomWebView )Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient2(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge2(this), "jsBridge2");
Control.LoadUrl($"file:///android_asset/Content/{((CustomWebView )Element).Uri}");
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((CustomWebView )Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
JavascriptWebViewClient2.cs:
public class JavascriptWebViewClient2 : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient2(CustomWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
JSBridge2.cs:
public class JSBridge2 : Java.Lang.Object
{
readonly WeakReference<CustomWebViewRenderer > hybridWebViewRenderer;
public JSBridge2(CustomWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<CustomWebViewRenderer >(hybridRenderer);
}
[JavascriptInterface]
[Export("showDialog")]
public void ShowDialog(string data)
{
CustomWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((CustomWebView)hybridRenderer.Element).ShowDialog(data);
}
//string ret = "Alert :" + DateTime.Now;
//return ret;
}
}
I followed the code sample in the link below. https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/customrenderers-hybridwebview/

How to open a page from App.xaml.cs if not open already

I have a Xaml.Forms app that uses FreshMVVM. I open a certain page from app.xaml.cs like this:
Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
{
var navService = FreshIOC.Container.Resolve<IFreshNavigationService>(FreshMvvm.Constants.DefaultNavigationServiceName);
Page page = FreshPageModelResolver.ResolvePageModel<SomePageModel>();
await navService.PushPage(page, null);
...
});
But I need to add a check to prevent doing this if this page is already open. How can I make such a check?
Add a static bool value in the App class to check if the page has been opened:
public partial class App : Application
{
public static bool isPageOpened;
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
public void test()
{
if (App.isPageOpened = false)
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(async () =>
{
var navService = FreshIOC.Container.Resolve<IFreshNavigationService>(FreshMvvm.Constants.DefaultNavigationServiceName);
Page page = FreshPageModelResolver.ResolvePageModel<SomePageModel>();
App.isPageOpened = true;
await navService.PushPage(page, null);
});
}
}
}
And in the page's OnDisappearing method, set the isPageOpened to false:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
App.isPageOpened = false;
}
}

Xamarin Forms WebView CanGoForward property is inaccurate on sites that utilize the History API

I am having an issue that relates to a post on GitHub. User nirbil summarizes it best:
If a website utilizes the historyApi then the WebView's CanGoBack CanGoForward are wrong.
Steps to reproduce -
Set the webview's source to a single page web application
Navigate within the application to a different url (should utilize
the pushstate)
Navigate back -> CanGoForward changes to true
Navigate within the application again by following some link ->
CanGoForward erroneously does not change to false
On windows this behavior can be traced to the webview's renderer - https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
Here is the original post on GitHub https://github.com/xamarin/Xamarin.Forms/issues/7691
A lot of ideas are suggested on how to solve the problem however, my grasp of Xamarin Forms is somewhat lacking. Any ideas on how to solve the issue?
You could add the source of webview to use renderer. I use the code sample on GitHub. https://github.com/xamarin/Xamarin.Forms/issues/7691
The original:
Set the source of webview.
<WebView x:Name="webView" Source="https://www.google.com" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></WebView>
Now:
This question on GitHub has been collected. You could follow it on GitHub. It would be fixed soon.
I solved it using a custom Xamarin Forms WebView with custom CanGoBack/Forward properties.
You can find the it here:
https://github.com/nirbil/XF.CanGoWebView
Following are the main bits of the solution.
CustomWebView control with new bindable properties:
public class CustomWebView : WebView
{
public CustomWebView() {}
public static BindableProperty CustomCanGoForwardProperty =
BindableProperty.Create(
nameof(CustomCanGoForward),
typeof(bool),
typeof(CustomWebView),
false,
BindingMode.OneWayToSource);
public static BindableProperty CustomCanGoBackProperty =
BindableProperty.Create(
nameof(CustomCanGoBack),
typeof(bool),
typeof(CustomWebView),
defaultValue: false,
BindingMode.OneWayToSource);
public bool CustomCanGoForward
{
get => (bool)GetValue(CustomCanGoForwardProperty);
set => SetValue(CustomCanGoForwardProperty, value);
}
public bool CustomCanGoBack
{
get => (bool)GetValue(CustomCanGoBackProperty);
set => SetValue(CustomCanGoBackProperty, value);
}
}
Custom WebViewRenderer on Android:
public class CustomWebViewRenderer : WebViewRenderer
{
protected override WebViewClient GetWebViewClient()
{
CustomWebViewClient webViewClient = new CustomWebViewClient(this);
webViewClient.AddressChanged += AddressChanged;
return webViewClient;
}
private void AddressChanged(string url)
{
if (Element is CustomWebView customWebView && Control != null)
{
customWebView.CustomCanGoBack = Control.CanGoBack();
customWebView.CustomCanGoForward = Control.CanGoForward();
}
}
}
Android CustomWebViewClient that intercepts changes and alerts the renderer:
public class CustomWebViewClient: FormsWebViewClient
{
public delegate void AddressChangedEventHandler(string url);
public event AddressChangedEventHandler AddressChanged;
public CustomWebViewClient(WebViewRenderer renderer) : base(renderer) {}
public override void DoUpdateVisitedHistory(WebView view, string url, bool isReload)
{
base.DoUpdateVisitedHistory(view, url, isReload);
AddressChanged?.Invoke(view.Url);
}
}
Custom WebViewRenderer on UWP:
public class CustomWebViewRenderer : WebViewRenderer
{
bool _registeredControl = false;
long _goBackRegistrationToken = 0;
long _goForwardRegistrationToken = 0;
protected override void Dispose(bool disposing)
{
UnregisterControl();
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
RegisterControl();
}
private void UnregisterControl()
{
if (Control != null && _registeredControl)
{
_registeredControl = false;
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, _goBackRegistrationToken);
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, _goForwardRegistrationToken);
}
}
private void RegisterControl()
{
if (Control != null && !_registeredControl)
{
_registeredControl = true;
_goBackRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoBackChanged));
_goForwardRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoForwardChanged));
}
}
private void OnWebViewCanGoBackChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoBack = Control.CanGoBack;
}
private void OnWebViewCanGoForwardChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoForward = Control.CanGoForward;
}
}

Page navigation through ViewModel using MVVMLight in windows 8

I just started developing my brand new windows 8 application last week using mvvm light.I am familiar with mvvmlight WP7 navigation. How i can achieve the same in windows 8. Can any one suggest a better method to achieve the same in windows 8. I found a solution, where we override onnavigated events in vm and handle navigate to other page. But i think that method is obsolete. Any one please guide me with the proper implementation. Thanks in advance.
I understand this is not the exact answer you may be looking for, but this may give you some ideas to explore.
In my case, I'm not using MVVMLight - but my own simple MVVM implementation. I use the BindableBase class (which comes with the default VS 2012 RC templates) for property notifications. I imagine, you could use MVVMLight to give you some of the infrastructure, which you can complement with something like the below.
For navigation, I define an interface that looks like:
public interface INavigationService
{
void Navigate(Type type);
void Navigate(Type type, object parameter);
void EnsureNavigated(Type pageType, object parameter);
bool CanGoBack { get; }
bool CanGoForward { get; }
void GoBack();
void GoForward();
IView CurrentView { get; }
}
And implement it as follows:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
public class NavigationService : INavigationService
{
private readonly Frame _frame;
public NavigationService(Frame frame)
{
_frame = frame;
_frame.Navigated += OnFrameNavigated;
}
private void OnFrameNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
var view = e.Content as IView;
if (view == null)
return;
var navMsg = new NavigationMessage()
{
Sender = this,
NewView = view,
Parameter = e.Parameter,
NavigationMode = (int)e.NavigationMode
};
EventManager.Current.Publish(navMsg);
//Anything that the parent needs to be notified should happen in of after this method
var viewModel = view.ViewModel;
if (viewModel != null)
viewModel.Initialise(e.Parameter);
}
public void Navigate(Type pageType)
{
DisposePreviousView();
_frame.Navigate(pageType);
}
public void Navigate(Type pageType, object parameter)
{
DisposePreviousView();
_frame.Navigate(pageType, parameter);
}
private void DisposePreviousView()
{
var currentView = this.CurrentView;
var currentViewDisposable = currentView as IDisposable;
if (currentViewDisposable != null)
{
currentViewDisposable.Dispose();
currentViewDisposable = null;
} //view model is disposed in the view implementation
}
public void EnsureNavigated(Type pageType, object parameter)
{
var currentView = this.CurrentView;
if (currentView == null || currentView.GetType() != pageType)
{
Navigate(pageType, parameter);
}
}
public IView CurrentView
{
get { return _frame.Content as IView; }
}
public bool CanGoBack
{
get { return _frame != null && _frame.CanGoBack; }
}
public void GoBack()
{
// Use the navigation frame to return to the previous page
if (_frame != null && _frame.CanGoBack) _frame.GoBack();
}
public bool CanGoForward
{
get { return _frame != null && _frame.CanGoForward; }
}
public void GoForward()
{
// Use the navigation frame to return to the previous page
if (_frame != null && _frame.CanGoForward) _frame.GoForward();
}
}
IView:
public interface IView : IDisposable
{
IViewModel ViewModel { get; }
void Refresh();
}
IViewModel:
public interface IViewModel : INotifyPropertyChanged, IDisposable
{
void Initialise(object parameter);
string ViewTitle { get; }
void Refresh();
}
Finally, in the XAML page, define a Frame element:
<Frame x:Name="ContentFrame" />
And in the code-behind of the page: (this in the only ugly part in my opinion - but its hopefully not too bad):
var _navigationService = new NavigationService(this.ContentFrame);
You can now pass the _navigationService to the viewmodel. In my case I create the viewmodel in the code-behind of the page:
public HomePage()
{
this.InitializeComponent();
var _navigationService = NavigationService.GetFor(this.ContentFrame);
DataContext = new HomePageViewModel(_navigationService);
}
Hope this helps.
Read the article published in MSDN Magazine just recently by Laurent Bugnion himself on working with the MVVM Light Toolkit and Windows 8.
Towards the end of the article he explains exactly how to setup the NavigationService you need.
http://msdn.microsoft.com/en-us/magazine/jj651572.aspx
The NavigationService that was in MVVMLight has been migrated in a new package called WinRTBehaviors. You can also get EventToCommand in Win8nl, both from nuget. See my blog posted here:
Getting Started w/ MVVM Light for Windows 8, EventToCommand and Behaviors
http://blog.tattoocoder.com/2012/08/getting-started-w-windows-8-mvvm-light.html

Resources