Tutoriel 8 - Le rendre lisse¶
Jusqu'à présent, notre application a été relativement simple : affichage des widgets de l'interface graphique, appel d'une bibliothèque tierce simple et affichage des résultats dans une boîte de dialogue. Toutes ces opérations se déroulent très rapidement et notre application reste réactive.
Cependant, dans une application réelle, nous devrons effectuer des tâches ou des calculs complexes qui peuvent prendre un certain temps, et nous voulons que notre application reste réactive au fur et à mesure de l'exécution de ces tâches. Modifions notre application pour qu'elle prenne un peu de temps et voyons les changements à apporter pour tenir compte de ce comportement.
Accéder à une API¶
L'une des tâches les plus courantes et les plus fastidieuses qu'une application doit effectuer consiste à demander à une API web de récupérer des données et de les afficher à l'utilisateur. Les API web mettent parfois une ou deux secondes à répondre, de sorte que si nous appelons une API de ce type, nous devons veiller à ce que notre application ne devienne pas inactive pendant que nous attendons que l'API web renvoie une réponse.
Il s'agit d'une application ludique, nous ne disposons donc pas d'une
véritable API avec laquelle travailler. Nous utiliserons donc un exemple de
point de terminaison API comme source de données. Si vous ouvrez
https://tutorial.beeware.org/tutorial/message.json
dans votre navigateur, vous obtiendrez une charge utile JSON avec un message.
La bibliothèque standard de Python contient tous les outils dont vous avez besoin pour accéder à une API. Cependant, les API intégrées sont de très bas niveau. Ce sont de bonnes implémentations du protocole HTTP, mais elles exigent de l'utilisateur qu'il gère de nombreux détails de bas niveau, comme la redirection d'URL, les sessions, l'authentification et l'encodage des données utiles. En tant qu'"utilisateur de navigateur normal", vous avez probablement l'habitude de considérer ces détails comme allant de soi, puisque le navigateur les gère pour vous.
En conséquence, des bibliothèques tierces ont été développées pour envelopper
les API intégrées et fournir une API plus simple qui correspond mieux à
l'expérience quotidienne du navigateur. Nous allons utiliser l'une de ces
bibliothèques pour accéder à l'API {JSON} Placeholder API - une bibliothèque
appelée httpx. Briefcase utilise httpx en
interne, donc elle est déjà dans votre environnement local - vous n'avez pas
besoin de l'installer séparément pour l'utiliser ici.
Ajoutons un appel API httpx à notre application. Modifions le paramètre
requires dans notre pyproject.toml pour inclure la nouvelle exigence :
requires = [
"faker",
"httpx",
]
Ajoutez un import au début de app.py pour importer httpx :
import httpx
Puis modifiez le callback say_hello() pour qu'il ressemble à ceci :
async def say_hello(self, widget):
fake = faker.Faker()
with httpx.Client() as client:
response = client.get("https://tutorial.beeware.org/tutorial/message.json")
payload = response.json()
await self.main_window.dialog(
toga.InfoDialog(
greeting(self.name_input.value),
f"A message from {fake.name()}: {payload['body']}",
)
)
Ceci modifiera le callback say_hello() de telle sorte que lorsqu'il est
invoqué, il le fera :
- effectuer une requête GET sur l'API JSON pour obtenir le poste 42 ;
- décoder la réponse en JSON ;
- extraire le corps du message ; et
- inclure le corps de ce message dans le texte de la boîte de dialogue "message", à la place du texte généré par Faker.
Lançons notre application mise à jour dans le mode développeur de Briefcase pour
vérifier que notre changement a fonctionné. Comme nous avons ajouté une nouvelle
exigence, nous devons demander au mode développeur de réinstaller les exigences,
en utilisant l'argument -r :
(beeware-venv) $ briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Lorsque vous entrez un nom et que vous appuyez sur le bouton, une boîte de dialogue doit s'afficher :

(beeware-venv) $ briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Lorsque vous entrez un nom et que vous appuyez sur le bouton, une boîte de dialogue doit s'afficher :

(beeware-venv) C:\...>briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Lorsque vous entrez un nom et que vous appuyez sur le bouton, une boîte de dialogue doit s'afficher :

Vous ne pouvez pas exécuter une application Android en mode développeur - utilisez les instructions pour la plateforme de bureau que vous avez choisie.
Vous ne pouvez pas exécuter une application iOS en mode développeur - utilisez les instructions pour la plateforme de bureau que vous avez choisie.
À moins que vous ne disposiez d'une connexion internet très rapide, vous remarquerez peut-être que lorsque vous appuyez sur le bouton, l'interface graphique de votre application se bloque pendant un petit moment. Le système d'exploitation peut même manifester ce blocage par un curseur "beachball" ou "spinner" pour indiquer que l'application ne répond pas.
A moins que vous ne disposiez d'une connexion internet très rapide, vous remarquerez peut-être que lorsque vous appuyez sur le bouton, l'interface graphique de votre application se bloque pendant un petit moment. C'est parce que la requête web que nous avons faite est synchrone. Lorsque notre application effectue la requête web, elle attend que l'API renvoie une réponse avant de continuer. Pendant cette attente, l'API ne permet pas à l'application de se redessiner, ce qui a pour effet de bloquer l'application.
Boucles d'événements de l'interface graphique¶
Pour comprendre pourquoi cela se produit, nous devons entrer dans les détails du fonctionnement d'une application GUI. Les spécificités varient en fonction de la plate-forme, mais les concepts de haut niveau sont les mêmes, quelle que soit la plate-forme ou l'environnement d'interface graphique que vous utilisez.
Une application GUI est, fondamentalement, une boucle unique qui ressemble à quelque chose comme :
while not app.quit_requested():
app.process_events()
app.redraw()
Cette boucle est appelée boucle d'événements. (Il ne s'agit pas de noms de méthodes réels, mais d'une illustration de ce qui se passe dans le "pseudo-code").
Lorsque vous cliquez sur un bouton, faites glisser une barre de défilement ou
tapez une touche, vous générez un "événement". Cet "événement" est placé dans
une file d'attente, et l'application traitera la file d'événements lorsqu'elle
en aura l'occasion. Le code utilisateur déclenché en réponse à l'événement est
appelé event handler (gestionnaire d'événement). Ces gestionnaires
d'événements sont invoqués dans le cadre de l'appel process_events().
Une fois qu'une application a traité tous les événements disponibles, elle va
redraw() l'interface graphique. Cela prend en compte tous les changements que
les événements ont causés à l'affichage de l'application, ainsi que tout ce qui
se passe dans le système d'exploitation - par exemple, les fenêtres d'une autre
application peuvent masquer ou révéler une partie de la fenêtre de notre
application, et le redessin de notre application devra refléter la partie de la
fenêtre qui est actuellement visible.
Détail important : pendant qu'une application traite un événement, elle ne peut pas redessiner, et elle ne peut pas traiter d'autres événements.
Cela signifie que toute logique utilisateur contenue dans un gestionnaire d'événements doit être exécutée rapidement. Tout retard dans l'exécution du gestionnaire d'événements sera observé par l'utilisateur sous la forme d'un ralentissement (ou d'un arrêt) des mises à jour de l'interface graphique. Si ce délai est suffisamment long, votre système d'exploitation peut signaler qu'il s'agit d'un problème - les icônes macOS "beachball" et Windows "spinner" indiquent que votre application prend trop de temps dans un gestionnaire d'événements.
Des opérations simples comme "mettre à jour une étiquette" ou "recalculer le total des entrées" sont faciles à réaliser rapidement. Cependant, de nombreuses opérations ne peuvent pas être effectuées rapidement. Si vous effectuez un calcul mathématique complexe, si vous indexez tous les fichiers d'un système de fichiers ou si vous effectuez une requête réseau importante, vous ne pouvez pas "faire vite" - les opérations sont intrinsèquement lentes.
Alors, comment effectuer des opérations à long terme dans une application GUI ?
Programmation asynchrone¶
Ce dont nous avons besoin, c'est d'un moyen de dire à une application au milieu d'un gestionnaire d'événements de longue durée qu'il est acceptable de relâcher temporairement le contrôle dans la boucle d'événements, tant que nous pouvons reprendre là où nous nous sommes arrêtés. C'est à l'application de déterminer quand cette libération peut avoir lieu ; mais si l'application libère le contrôle dans la boucle d'événements régulièrement, nous pouvons avoir un gestionnaire d'événements de longue durée et maintenir une interface utilisateur réactive.
Nous pouvons le faire en utilisant la programmation asynchrone. La programmation asynchrone est une façon de décrire un programme qui permet à l'interpréteur d'exécuter plusieurs fonctions en même temps, en partageant les ressources entre toutes les fonctions qui s'exécutent simultanément.
Les fonctions asynchrones (appelées co-routines) doivent être explicitement déclarées comme étant asynchrones. Elles doivent également déclarer en interne lorsqu'il est possible de changer de contexte et de passer à une autre co-routine.
En Python, la programmation asynchrone est implémentée à l'aide des mots-clés
async et await, et du module
asyncio dans la bibliothèque
standard. Le mot-clé async nous permet de déclarer qu'une fonction est une
co-routine asynchrone. Le mot-clé await permet de déclarer qu'il existe une
opportunité de changer de contexte vers une autre co-routine. Le module
`asyncio](https://docs.python.org/3/library/asyncio.html) fournit d'autres
outils et primitives utiles pour le codage asynchrone.
Rendre le didacticiel asynchrone¶
Pour rendre notre tutoriel asynchrone, modifiez le gestionnaire d'événement
say_hello() pour qu'il ressemble à ceci :
async def say_hello(self, widget):
fake = faker.Faker()
async with httpx.AsyncClient() as client:
response = await client.get("https://jsonplaceholder.typicode.com/posts/42")
payload = response.json()
await self.main_window.dialog(
toga.InfoDialog(
greeting(self.name_input.value),
f"A message from {fake.name()}: {payload['body']}",
)
)
Il n'y a que 4 changements dans ce code par rapport à la version précédente :
- Le client créé est un
AsyncClient()asynchrone, plutôt qu'unClient()synchrone. Cela indique àhttpxqu'il doit fonctionner en mode asynchrone, plutôt qu'en mode synchrone. - Le gestionnaire de contexte utilisé pour créer le client est marqué comme
async. Cela indique à Python qu'il y a une opportunité de relâcher le contrôle lorsque le gestionnaire de contexte est entré et sorti. - L'appel
getest fait avec un mot-cléawait. Cela indique à l'application que pendant que nous attendons la réponse du réseau, l'application peut laisser le contrôle à la boucle d'événements. Nous avons déjà vu ce mot-clé auparavant - nous utilisons égalementawaitlors de l'affichage de la boîte de dialogue. La raison de cette utilisation est la même que pour la requête HTTP - nous devons dire à l'application que pendant que la boîte de dialogue est affichée, et que nous attendons que l'utilisateur appuie sur un bouton, il est acceptable de redonner le contrôle à la boucle d'événements.
Il est également important de noter que le gestionnaire lui-même est défini
comme async def, plutôt que comme def. Cela indique à Python que la méthode
est une coroutine asynchrone. Nous avons fait ce changement dans le Tutoriel 3
lorsque nous avons ajouté la boîte de dialogue. Vous ne pouvez utiliser les
instructions await qu'à l'intérieur d'une méthode déclarée comme async def.
Toga vous permet d'utiliser des méthodes normales ou des co-programmes asynchrones en tant que gestionnaires ; Toga gère tout en coulisses pour s'assurer que le gestionnaire est invoqué ou attendu selon les besoins.
Si vous sauvegardez ces changements et relancez l'application en mode développement, il n'y aura pas de changements évidents dans l'application. Cependant, lorsque vous cliquez sur le bouton pour déclencher le dialogue, vous pouvez remarquer un certain nombre d'améliorations subtiles :
- Le bouton revient à l'état "décliqué" au lieu d'être bloqué à l'état "cliqué".
- L'icône "beachball"/"spinner" n'apparaît pas.
- Si vous déplacez ou redimensionnez la fenêtre de l'application en attendant que la boîte de dialogue s'affiche, la fenêtre se redessinera.
- Si vous essayez d'ouvrir un menu d'application, le menu s'affiche immédiatement.
Nous pouvons maintenant exécuter l'application complète. Cependant, comme nous
avons ajouté une exigence supplémentaire (httpx), nous devons également mettre
à jour les exigences de notre application ; nous pouvons le faire en passant
-r à briefcase run. Cela mettra à jour les exigences de notre application,
puis recompilera l'application, et enfin la lancera :
(beeware-venv) $ briefcase run -r
(beeware-venv) $ briefcase run -r
(beeware-venv) C:\...>briefcase run -r
(beeware-venv) $ briefcase run android -r
(beeware-venv) $ briefcase run iOS -r
Vous devriez voir votre application fonctionner et rester réactive lorsque vous appuyez sur le bouton et que le contenu du réseau est récupéré.
Étapes suivantes¶
Il s'agit d'un avant-goût de ce que vous pouvez faire avec les outils fournis par le projet BeeWare. Au cours de ce tutoriel, vous avez :
- Création d'un nouveau projet d'application GUI ;
- Exécuter l'application en mode développeur
- L'application a été conçue comme un binaire autonome pour un système d'exploitation de bureau ;
- Il a emballé ce projet pour le distribuer à d'autres personnes ;
- Exécuter l'application sur un simulateur et/ou un appareil mobile ;
- Exécuter l'application en tant qu'application web ;
- Ajout d'une dépendance tierce à votre application ; et
- Modifier l'application pour qu'elle reste réactive.
Alors, quelle est la suite des événements ?
- Si vous souhaitez aller plus loin, il existe d'autres tutoriels thématiques qui abordent en détail des aspects spécifiques du développement d'applications.
- Si vous souhaitez en savoir plus sur la manière de construire des interfaces utilisateur complexes avec Toga, vous pouvez consulter la documentation de Toga. Toga dispose également de son propre tutoriel démontrant comment utiliser les différentes fonctionnalités de la boîte à outils de widgets.
- Si vous souhaitez en savoir plus sur les capacités de Briefcase, vous pouvez consulter la documentation de Briefcase.