Xamarin Android WebView not opening links in browser once SetWebViewClient is called - xamarin

I have a simple webview that is loading some locally stored HTML. There are plain vanilla links in the HTML, like <a href="https://google.com"/>. When a user taps on one of these links, I want the device browser to open.
This is the default behaviour for an Android webview and seems to work fine, however as soon as I call the webView.SetWebViewClient(webViewClient) method, this default behaviour stops.
I've tried overriding the ShouldOverrideUrlLoading() method(s) in my implementation of the WebViewClient class to open the browser myself, but these methods are never getting called.
To summarise, like this, links in my HTML will open in the browser:
webView = new WebView(Activity);
//var webViewClient = new WebViewClient();
//webView.SetWebViewClient(webViewClient);
webView.LoadData(myHTML, "text/html", null);
But as soon as I uncomment those two lines, the links will open within the WebView.

I'm not sure why your ShouldOverrideUrlLoading method is not being called in your class as I don't have all your code to look at. So, I put together a fully working example where you can control how the links are opened--whether in your webview view or another Android browser activity. I hope it helps!
WebView.axml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/WebView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent">
<WebView
android:id="#+id/webviewMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF" />
</LinearLayout>
testwebview.html ("Assets/html/testwebview.html" in project)
<html>
<head>
<title>WebView Testing</title>
</head>
<body>
<h1>Select a link...</h1>
<br />
Google
<br />
Stack Overflow
</body>
</html>
WebViewCustomOverrideActivity.cs
using Android.App;
using Android.OS;
using Android.Webkit;
using Android.Content;
namespace XamdroidMaster.Activities {
[Activity(Label = "Custom WebView Testing", MainLauncher = true)]
public class WebViewCustomOverrideActivity : Activity {
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.WebView);
WebView wv = FindViewById<WebView>(Resource.Id.webviewMain);
MyWebViewClient myWebViewClient = new MyWebViewClient();
wv.SetWebViewClient(myWebViewClient);
wv.LoadUrl("file:///android_asset/html/testwebview.html"); // "Assets/html/testwebview.html" in project
}
}
public class MyWebViewClient : WebViewClient {
public override bool ShouldOverrideUrlLoading(WebView view, string url) {
if (url.ToLower().Contains("google.com")) {
// Let my WebView load the page
return false;
}
// For all other links, launch another Activity that handles URLs
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse(url));
view.Context.StartActivity(intent);
return true;
}
}
}

Related

Pushing content page from background thread freezes app

In Xamarin Forms for iOS, I have a custom renderer for a ContentPage that displays a video control. In my Xamarin Forms app, this custom ContentPage is displayed inside a NavigationPage.
I would like to have the video screen open when a specific message comes in via MQTT.
When I open the video page by clicking a link on the main screen, it opens as expected. I know I am receiving the message via MQTT and calling Navigation.PushModalAsync() because of console statements and breakpoints. However, the custom rendered page is not displayed and the UI of my app freezes each time after calling PushModalAsync.
Is there something else I need to do to trigger Navigation.PushModalAsync() based on receiving an MQTT notification in the background of my app?
ViewRoomsPage.axml.cs:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ViewRoomsPage : ContentPage
{
public ViewRoomsPage()
{
InitializeComponent();
}
public string StreamUri { get; set; }
}
ViewRoomsPage.axml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyForms.Pages.ViewRoomsPage">
<ContentPage.Content>
</ContentPage.Content>
VideoViewerRenderer.cs (video code removed; this should display a blank red screen. It also works when launched from a button on the main screen)
[assembly: ExportRenderer(typeof(ViewRoomsPage), typeof(ViewRoomsRenderer))]
namespace MyForms.IOS.NativeImplementations
{
public class ViewRoomsRenderer : PageRenderer
{
private IJKFFMoviePlayerController _playerController;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
e.NewElement.BackgroundColor = Color.Red;
}
}
}
Method triggered from receiving an MQTT message
public void PushViewRooms()
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(new ViewRoomsPage());
});
}
In App.xaml.cs:
public partial class App : Application
{
public App()
{
SetupDependencies(); // using StructureMap
Manager = DependencyContainer.Resolve<IMqttManager>();
Manager.Connect();
InitializeComponent();
var mainPage = new MainPage();
MainPage = new NavigationPage(mainPage);
}
}
The problem was a deadlock caused by a Task.WaitAll() being triggered in another section of code running in the background.
Thanks all who helped sanity check that it wasn't something in the way the renderer was set up.

Add Page on Navigate Drawer Menu and navigate to respective page on menu click Xamarin Android

I have made a sample with Navigate Drawer Menu from Left side. I have fixed 4 menu items as (Home, About Us, Enquiry, Contact Us).
Now I need to add pages to my layout, By default it should show Home Page with respective content. Then when I would click on About Us menu item then should display About Us Page with respective content...so on for other menu links too.
I didn't have an idea to how to add page and how should I make menu clickable and to track which menu item should have been clicked and which page should have to open and how.
I have searched a lot for this on google, on YouTube and on several tutorials, but didn't found any proper guidance. Therefore kindly help me regarding this please....
below it the code of MainActivity.cs
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Support.V7.App;
using Android.Support.V4.Widget;
using V7Toolbar = Android.Support.V7.Widget.Toolbar;
using Android.Support.Design.Widget;
namespace NavigationDrawerLayout
{
[Activity(Label = "J&K Tour and Travel", Theme = "#style/Theme.DesignDemo", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : AppCompatActivity
{
DrawerLayout drawerLayout;
NavigationView navigationView;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
// Create ActionBarDrawerToggle button and add it to the toolbar
var toolbar = FindViewById<V7Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
var drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, Resource.String.drawer_open, Resource.String.drawer_close);
drawerLayout.SetDrawerListener(drawerToggle);
drawerToggle.SyncState();
navigationView = FindViewById<NavigationView>(Resource.Id.nav_view);
setupDrawerContent(navigationView);
}
void setupDrawerContent(NavigationView navigationView)
{
navigationView.NavigationItemSelected += (sender, e) => {
//e.MenuItem.SetChecked(true);
drawerLayout.CloseDrawers();
};
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
navigationView.InflateMenu(Resource.Menu.nav_menu);
return true;
}
}
}
Also i am sharing a link of .rar file of my whole sample project below. I have made this project using VS 2017 Community Edition using c#:
https://drive.google.com/open?id=0B584mT-OF6vJZzdFem4tMG9jV1U
It is recommended to use NavigationDrawer with Fragment to show different pages.
In your layout you can place a FrameLayout for the container of fragments for example:
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout android:id="#+id/framelayout"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<android.support.design.widget.NavigationView
android:layout_width="wrap_content"
android:layout_gravity="start"
android:layout_height="match_parent"
android:id="#+id/nav_view"
app:menu="#menu/nav_menu"
app:headerLayout="#layout/drawer_header" />
</android.support.v4.widget.DrawerLayout>
Then in the OnCreate method first commit your home page like this:
FragmentTransaction transaction = this.FragmentManager.BeginTransaction();
HomeFragment home = new HomeFragment();
transaction.Add(Resource.Id.framelayout, home).Commit();
Then in the NavigationItemSelected event:
var naviview = FindViewById<NavigationView>(Resource.Id.nav_view);
naviview.NavigationItemSelected += (sender, e) =>
{
e.MenuItem.SetChecked(true);
FragmentTransaction transaction1 = this.FragmentManager.BeginTransaction();
switch (e.MenuItem.TitleFormatted.ToString())
{
case "Home":
HomeFragment home = new HomeFragment();
transaction1.Replace(Resource.Id.framelayout, home).AddToBackStack(null).Commit();
break;
case "About Us":
VideoFragment video = new VideoFragment();
transaction1.Replace(Resource.Id.framelayout, video).AddToBackStack(null).Commit();
break;
}
drawerLayout.CloseDrawers();
};
Since it is Fragment, then for example, your Home Page should be something like this:
public class HomeFragment : Fragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.homelayout, container, false);
return view;
}
}

How to make Zebra Xing (Zxing) as subview in Xamarin Android

In my Xamarin.Android app, I want to use ZXing to scan barcode. I want to display the scanner in the view of an activity.
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="5">
<Button
android:text="Scan with Default Overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/buttonScanDefaultView"
android:layout_weight="1" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/scanView"
android:layout_weight="2" />
</LinearLayout>
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
scannerFragment = new ZXingScannerFragment ();
scannerFragment.CustomOverlayView = CustomOverlayView;
scannerFragment.UseCustomOverlayView = UseCustomOverlayView;
scannerFragment.TopText = TopText;
scannerFragment.BottomText = BottomText;
this.FragmentManager.BeginTransaction ()
.Replace (Resource.Id.scanView, scannerFragment, "ZXINGFRAGMENT")
.Commit ();
}
I'm getting an error stating that I cannot convert support.v4.fragment into android.app.Fragment.
Can anyone advise what I'm doing wrong and how should I approach this to get the scanner view (of ZXing) in a layout of my current activity.
ZXingScannerFragment derives from Android.Support.V4.App.Fragment while the Activity.FragmentManager expects fragments derived from Android.App.Fragment.
Now, how to fix that:
Inherit your activity from any activity that works with Android.Support.V4. Easiest would to use Android.Support.V4.App.FragmentActivity from package Xamarin.Android.Support.v4 that is already installed by ZXing.Net.Mobile package as a dependency.
When you have correct activity, you can use this.SupportFragmentManager instead of this.FragmentManager to work with Support.V4-based fragments.
So, layout you have is good. Code should be updated to something like:
using Android.App;
using Android.Widget;
using Android.OS;
using ZXing.Mobile;
using Android.Support.V4.App;
namespace ZXingSample
{
[Activity(Label = "ZXing Sample", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : FragmentActivity
{
int count = 1;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
var scannerFragment = new ZXingScannerFragment();
scannerFragment.UseCustomOverlayView = false;
scannerFragment.TopText = "Scan your code";
scannerFragment.BottomText = "Then proceed";
this.SupportFragmentManager.BeginTransaction()
.Replace(Resource.Id.scanView, scannerFragment, "ZXINGFRAGMENT")
.Commit();
}
}
}
Launching the app, you will see your scanner:

reaching a page from xamarin.forms inside android project

I'm working on an android app with xamarin. I made tabbed pages. For the second page, i want to show the camerastream from my android camera. For that, some sample code said me I need to use a textureView inside the android part of the app, but that textureview needs to be putted on that second page. Whenever I try to reach a Stacklayout inside that Page, the following error shows up: 'Page1.camera' is inaccessible due to its protection level.
Using x:FieldModifier="public" inside that stacklayout doesn't work either.
Here is the structure of my code to make it more clear
Here I make the tabbed pages:
MainPage = new TabbedPage
{
Children = {
new MainPage(),
new Page1(),
new Page2()
}
};
Inside that Page1 i have this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App4.Page1"
Title="Licht">
<StackLayout x:Name="camera" x:FieldModifier="public" Orientation="Vertical">
</StackLayout>
</ContentPage>
And inside the MainActivity.cs i have this where i have to access the camera.
_textureView = new TextureView(Page1.camera);
And this is the structure of my app
And this is the structure of my app
Using x:FieldModifier="public" inside that stacklayout doesn't work either.
I have tried x:FieldModifier="public"in xamarin form, even though I use x:FieldModifier="public", the "camera" property is still private. This feature is not useful.
As far as I know there is no way to access "camera" inside MainActivity.cs in Xamarin form.
As a workaround you can design a page render for android platform and create a TextureView in your page render code.
how to create a page render
_textureView = new TextureView(Page1.camera);
BTW to initialize TextureView you need the object that implements the ISurfaceTextureListener interface at android platform.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity,ISurfaceTextureListener
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
TextureView textureView = new TextureView(this);
textureView.SurfaceTextureListener = this;
LoadApplication(new App());
}
public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
{
//start
}
public bool OnSurfaceTextureDestroyed(SurfaceTexture surface)
{
//stop
}
public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
{
throw new NotImplementedException();
}
public void OnSurfaceTextureUpdated(SurfaceTexture surface)
{
throw new NotImplementedException();
}
}
Please follow the TextureView guide for Android.

Xamarin page navigation

How do i make a page slide into the Content section when i choose the menu options ?
Currently when i select a menu option it opens a brand new page on top of my main page(page on the right).
I want it to work like html IFrame. So i want the page to load without refreshing the Menu toolbar on the right page - and of course the menu must disappear when the new page slide in.
Here is the code i use to call my another page from the menu option :
C#
[Activity(Label = "FlyInMenu", MainLauncher = true, Theme = "#android:style/Theme.Holo.Light.NoActionBar")]
public class Activity1 : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
//for calling the menu opiton
var menu = FindViewById<FlyOutContainer>(Resource.Id.FlyOutContainer);
var menuButton = FindViewById(Resource.Id.MenuButton);
menuButton.Click += (sender, e) =>
{
menu.AnimatedOpened = !menu.AnimatedOpened;
};
//redirect
var myProfileOption = FindViewById(Resource.Id.linearLayout11);
myProfileOption.Click += delegate {
var intent = new Intent(this, typeof(MyProfileActivity));
StartActivity(intent);
};
}
}
AXML
<LinearLayout
android:orientation="horizontal"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/linearLayout1"
android:layout_margin="8dp"
android:duplicateParentState="true">
<ImageView
android:src="#drawable/user_icon"
android:layout_width="29.0dp"
android:layout_height="44.0dp"
android:id="#+id/imageView1"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp" />
<TextView
android:text="Option1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView2"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:textColor="#878787"
android:textSize="18dp" />
</LinearLayout>
Another question is that how do i implement a loading page between the calls ? I.e when i click on a menu option
i would like to see a loading page an then the destination page.
You are looking for a NavigationView and DrawerLayout which is provided by the Android Design Support Library.
Xamarin provides bindings for this library. They also have a example on how to use this NavigationView (see section "Effective Navigation in Android")
I found the solution here : http://www.appliedcodelog.com/2016/01/navigation-drawer-using-material-design.html
This tutorial switch pages smoothly, without flickering or affecting the toolbar.

Resources