Personne n’aime les contraintes, mais il en faut parfois, surtout avec une WebView qui peut laisser l’utilisateur cliquer sur un lien qu’on ne veut pas laisser s’ouvrir au milieu de notre App… Comment contrôler le contrôle ? Simplement avec un Behavior…
Le WebView
Il arrive parfois qu’on ait besoin d’ouvrir une page html dans une partie d’une page MAUI. Il peut s’agir d’une page web affichant des infos complémentaires sur un produit par exemple. Mais elle peut contenir des liens.
Le WebView est un outil précieux car c’est un contrôle système dans Android, parfaitement trusted et sans aucun habillage ce qui permet de l’utiliser comme toute autre view dans une mise en page plus complète.
Mais justement, sans aucun habillage cela veut dire qu’il n’y a aucun bouton ni aucune astuce pour naviguer et encore moins pour “revenir en arrière”. Mais les liens sont fonctionnels…
Imaginons qu’au milieu d’une fiche produit on souhaite afficher une partie de sa fiche telle qu’elle est présentée sur le site de l’entreprise, cette page web aura aussi des liens très probablement. Si l’utilisateur “clic” sur l’un de ces derniers la WebView va naviguer. Ce qui va faire “sortir” l’utilisateur de la zone prévue sans possibilité de retour. L’affichage va devenir erratique et incohérent et l’utilisateur sera perdu car tout cela aura lieu à l’intérieur de la WebView qui se trouve sur notre page MAUI. Le contenu n’aura plus rien à voir avec celui de départ et sans navigation arrière l’utilisateur risque fort de connaître une expérience de dissonance cognitive !
Il faut absolument empêcher cela.
Bloquer la navigation ?
On part du principe que dans la WebView on ne va afficher que des sites qu’on connait et qui ne pointent pas n’importe où ni qui possèdent des liens qui vont afficher le catalogue SM de Jacquie et Michel … Donc on n’utilise que des sites de confiance en rapport direct avec l’App. Et certains liens peuvent même avoir malgré tout un intérêt pour l’utilisateur.
Bloquer toute la navigation est peut-être un peu trop restrictif. Mais laisser tout passer n’est pas assez fiable on l’a vu.
Fifty/fifty?
Comme souvent la bonne solution, ou la moins mauvaise selon l’angle de vue, consiste à couper la poire en deux. Ici il faudrait empêcher de naviguer à l’intérieur de la WebView mais autoriser la navigation à l’extérieur de l’App.
Comment ? En redirigeant la navigation de la WebView… Si le lien est un lien interne on laisse passer par exemple (un marqueur dans la page par exemple) mais si c’est un lien externe (de type http ou ftp, à chacun de voir où est la limite) alors on demande l’exécution de l’URL à un vrai navigateur qui : d’une part proposera des moyens de naviguer fonctionnels et d’autre part s’ouvrira au-dessus de l’App donc retournera à celle-ci lors d’une navigation arrière sur le smartphone (je parle bien de la navigation du smartphone ici et non de celle du browser !).
La page XAML utilisant la WebView ne sera donc pas perturbée par cette navigation html, mais l’utilisateur aura tout de même la possibilité de naviguer dans cette dernière.
Cela semble un bon compromis. Comment le mettre en œuvre ?
WebViewToBrowserBehavior
C’est le nom du Behavior que nous allons écrire et qui aura l’immense avantage de pouvoir décorer n’importe quelle WebView dès qu’on le voudra et ce sans toucher au contrôle WebView ni en faire une nouvelle classe.
Le code est vraiment simple :
public class WebViewToBrowserBehavior : BehaviorBase<WebView>
{
protected override void OnAttachedTo(WebView webView)
{
base.OnAttachedTo(webView);
webView.Navigating += WebViewOnNavigating;
}
protected override void OnDetachingFrom(WebView webView)
{
base.OnDetachingFrom(webView);
webView.Navigating -= WebViewOnNavigating;
}
private void WebViewOnNavigating(object sender, WebNavigatingEventArgs e)
{
if (!e.Url.StartsWith("https", StringComparison.InvariantCultureIgnoreCase))
return;
e.Cancel = true;
_ = Browser.OpenAsync(e.Url, this.LaunchOptions);
}
}
Ce Behavior est typé pour ne décorer que des WebView, pas de risque de mésusage. Ensuite son code est rudimentaire : quand il est accroché à une WebView il détourne son événement de navigation. Quand il est décroché il libère ce dernier. Et quand l’événement est déclenché il effectue un test (que vous pouvez supprimer, modifier, améliorer…) pour laisser passer les navigations considérées comme “ok” sinon il reroute l’URL vers le Browser externe… Ici le test se contente de filtrer tous les liens pour les obliger à pointer du Https. On peut établir une liste de sites autorisés, de pages autorisées, on pourrait ajouter un paramètre (une List<string> par exemple) qui permettrait de paramétrer le Behavior avec la liste des URL autorisées, voire ajouter un joker pour autoriser une branche de navigation entière ("https://www.e-naxos.com/*" par exemple) et bien d'autres choses encore.
Le test est là pour l’exemple des possibilités offertes par cette stratégie. On peut aussi le supprimer et toujours invoquer le browser externe. Ou on peut couper toutes les navigations, ou on peut encore modifier les URL avant de les exécuter… Et bien entendu tout cela peut faire l’objet de propriétés ajoutées au Behavior pour le rendre hyper souple. Chacun fera en fonction de ce qu’il pense être utile.
Comment utiliser ce Behavior ?
Voici un extrait de code montrant comment insérer le Behavior dans le fonctionnement de la WebView :
<ContentPage x:Class="AppTest.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:AppTest.Behaviors">
...
<WebView>
<WebView.Source>
<HtmlWebViewSource Html="{Binding DocumentText}" />
</WebView.Source>
<WebView.Behaviors>
<behaviors:WebViewToBrowserBehavior />
</WebView.Behaviors>
</WebView>
</ContentPage>
Conclusion
L’astuce est intéressante à connaître. Mais elle donne surtout l’occasion de parler encore des Behaviors qui sont une partie essentielle de XAML comme les convertisseurs de valeur ou le Visual State Manager… On oublie trop souvent de s’en servir alors que cela permet une programmation très propre qui ne réclame pas de créer de nouvelles classes pour répondre ponctuellement à un besoin particulier. Si vous pensez Behavior au lieu de penser C# et nouvelle classe alors vous pensez correctement en XAML !
Stay Tuned !