Notification et alarmes dans Flutter

Nov 10 2020
Utilisation du plug-in Flutter, Android_Alarm_Manager. Saches cela! Ce plugin ne fonctionne que pour la plateforme Android! Personnellement, je ne connais aucun équivalent iOS.

Utilisation du plug-in Flutter, Android_Alarm_Manager.

Saches cela! Ce plugin ne fonctionne que pour la plateforme Android ! Personnellement, je ne connais aucun équivalent iOS. Alternativement, un mois après la publication de cet article, je suis tombé sur un plugin qui fournit des notifications sur les plates-formes Android et iOS. J'ai, bien sûr, écrit un article et cela aussi. Voir ci-dessous.

Notifications dans Flutter

Sachez que si vous souhaitez utiliser le plugin décrit ici, vous devez suivre son fichier readme explicitement pour configurer correctement les choses. Vous êtes AndroidManfest.xml devrait au moins ressembler à ceci ci-dessous:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="NAME OF YOUR APPLICATION STARTING WITH COM.">
<!-- The INTERNET permission access.-->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- android_alarm_manager -->
    <!-- Start an Alarm When the Device Boots if past due -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- application needs to have the device stay on -->
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
        android:name="io.flutter.app.FlutterApplication"
        android:label="code_samples"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- android_alarm_manager -->
        <service
                android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
                android:permission="android.permission.BIND_JOB_SERVICE"
                android:exported="false"/>
        <receiver
                android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
                android:exported="false"/>
        <receiver
                android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
                android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

# https://pub.dev/packages/android_alarm_manager
android_alarm_manager: ^0.4.0

Dans ce cas, c'est pour déclencher des alarmes dans votre application! Dans mon cas, j'ai trouvé le plugin Flutter, android_alarm_manager , qui répondait aux besoins d'une application récente sur laquelle je travaillais et donc… j'ai fait une routine pour travailler facilement avec. Si vous le souhaitez, prenez-en une copie , personnalisez-la et partagez les améliorations que vous apportez. Cool?

Maintenant, si vous avez le temps, continuez à lire ce que j'ai trouvé que je devais faire pour que ce plugin fonctionne presque `` à toute épreuve '' afin de définir facilement et rapidement une alarme ou toute autre opération à effectuer `` en arrière-plan '' à un moment donné dans le futur pendant que l'application est en cours d'exécution. Cela rend la vie un peu plus facile, et c'est une bonne chose. Droite?

Captures d'écran uniquement. Cliquez pour Gists.

Comme toujours, je préfère utiliser des captures d'écran plutôt que des éléments essentiels pour afficher le code dans mes articles. Je les trouve plus faciles à utiliser et à lire. Cependant, vous pouvez cliquer / appuyer dessus pour voir le code dans un résumé ou dans Github. Ironiquement, il vaut mieux lire cet article sur le développement mobile sur votre ordinateur que sur votre téléphone. De plus, nous programmons principalement sur nos ordinateurs; pas sur nos téléphones. Pour l'instant.

Commençons.

Autres histoires de Greg Perry

Mon approche ici est de présenter d'abord cette classe utilitaire dans un exemple et de montrer comment l'utiliser - profitant ainsi du plugin Flutter, android_alarm_manager . En fait, j'utiliserai le même exemple répertorié dans la page d'exemple du plugin . Cependant, cet exemple a été modifié pour utiliser à la place le fichier de bibliothèque présenté ici. Une copie de cet exemple est à votre disposition sous le nom de base, android_alarm_manager . Après l'exemple, je vais parcourir certaines parties de la classe utilitaire elle-même en décrivant parfois ce qui devait être fait pour permettre à une telle classe d'être utilisée par les masses inflexibles qu'est le grand public.

Gardez-le statique

Dans cet exemple très simple, vous devez appuyer sur le bouton affiché, et 5 secondes plus tard, ces deux zéros comme illustré dans la capture d'écran ci-dessous se transformeront en uns. Wheeee! Ce qui est vraiment intéressant, bien sûr, c'est ce qu'il y a sous le capot dans le code.

Notez que j'ai changé le code de l'original pour prendre en charge les opérations asynchrones qui doivent être effectuées avant que l'application puisse réellement commencer à fonctionner. J'ai utilisé un widget FutureBuilder pour accomplir cela. Ce faisant, j'ai défini une nouvelle fonction appelée, initSettings () , pour initialiser la routine «Préférences partagées» utilisée pour «se souvenir» du nombre total d'alarmes déclenchées comme le montre la capture d'écran ci-dessus.

android_alarm_manager.dart

Vous pouvez également voir dans la fonction initSettings () affichée ci-dessous, elle définit le 'nombre total' à zéro si c'est la première fois que l'application s'exécute. C'est au début, cependant, que la routine de la bibliothèque, AlarmManager , est initialisée pour configurer le `` service d'alarme '' particulier nécessaire pour effectuer des notifications sur un téléphone Android.

android_alarm_manager.dart

Allons plus loin dans l'exemple de code et voyons ce qui compose ce petit bouton. Notez que la routine de la bibliothèque utilise les mêmes noms pour les paramètres et les fonctions qui composent le plugin Flutter soulignant, Android_Alarm_Manager . Mieux vaut être cohérent avec de telles choses. De plus, tout comme la propre fonction oneShot () du plugin , la version de cette bibliothèque, une fois appelée, «attendra» la durée spécifiée avant de lancer la routine de rappel spécifiée. Dans le cas de cet exemple modifié, la routine de rappel est une fonction anonyme qui s'exécutera après une durée de 5 secondes. Dans la capture d'écran ci-dessous, l'application a en fait redémarré le bouton enfoncé à nouveau indiquant, grâce aux préférences partagées, que le bouton a été enfoncé deux fois depuis sa première exécution. Wheeee.

android_alarm_manager.dart

Un examen plus approfondi de cette fonction anonyme ci-dessous, et nous voyons qu'il est passé un seul paramètre de type, entier. Vous pouvez deviner que c'est la même valeur d'identifiant transmise au deuxième paramètre en tant que nombre aléatoire à l'aide des fonctions Dart, Random (). NextInt (pow (2,31)) . Maintenant, pourquoi passer cette valeur alors qu'elle est déjà passée dans le paramètre juste à côté? Nous y reviendrons.

Pour l'instant, vous pouvez voir ci-dessous que la fonction de rappel, à son tour, appelle la fonction, _incrementCounter (). Là, le «nombre total» actuel de pressions sur les boutons est récupéré à partir de la routine «Préférences partagées». Il est ensuite mis à jour par un pour tenir compte de cette pression sur le bouton actuel et enregistré dans les préférences partagées. L'écran de l'application est ensuite mis à jour avec la variable incrémentée, _counter , à l'aide de la fonction setState (). Avez-vous suivi tout cela jusqu'à présent?

android_alarm_manager.dart

Rester statique

Contrairement à l'exemple d'origine (voir ci-dessous), cet exemple est autorisé à utiliser une fonction anonyme et n'est pas obligé d'utiliser spécifiquement une fonction statique. Bien sûr, vous êtes également autorisé à utiliser une fonction statique dans cet exemple - tant qu'elle accepte toujours cette seule valeur entière. Cependant, dans l'exemple d'origine, il doit s'agir d'une fonction statique ou d'une fonction de haut niveau définie en dehors de toute classe de ce fichier Dart de bibliothèque - l'un ou l'autre, vous voyez, est une exigence lorsque vous travaillez directement avec le plugin Flutter, Android_Alarm_Manager .

Cependant, si vous suivez mes articles, vous savez que j'aime les options. Il s'agit d'avoir des options avec moi. J'ai écrit cette classe utilitaire pour être plus accommodante - pour permettre des fonctions anonymes par exemple. Avec une telle disposition, par conséquent, le fait de passer cette valeur entière id en tant que paramètre permet en effet à la fonction d'être une fonction statique, une fonction de haut niveau ou une fonction anonyme. Options! Encore une fois, des captures d'écran de l'exemple d'origine suivent ci-dessous:

Passons à la classe utilitaire, AlarmManager , elle-même. Encore une fois, il est conçu pour fonctionner avec le plugin. Et encore une fois, de nombreux paramètres sont exactement les mêmes que ceux utilisés par le plugin et sont donc passés à ce plugin - mais pas avant de nombreux tests de paramètres pour les valeurs valides. Un autre trait nécessaire de ces classes d'utilité. Il fait tout le travail pour que vous n'ayez pas à le faire. Droite?

Dans la capture d'écran ci-dessous, se trouve la première partie de cette classe utilitaire. Dans sa fonction statique, init (), on voit que le plugin est bien initialisé. Notez que toutes les erreurs malheureuses qui peuvent se produire lors de la tentative d'initialisation seront interceptées dans l' instruction try-catch . Les classes d'utilité doivent faire cela aussi. Ensuite, dans la fonction init (), il y a la classe 'helper', _Callback , qui est appelée pour initialiser les moyens nécessaires pour que l'application communique avec l' isolat séparé utilisé par le service d'alarme en arrière-plan.

Enfin, vous pouvez voir ci-dessous que toutes les valeurs de paramètre non nulles sont affectées aux propriétés spécifiques et statiques qui composent la classe d'utilitaire, AlarmManager . Beaucoup de statique qui se passe ici n'est pas là.

alarm_manager.dart

Vous trouverez que la plupart des propriétés et fonctions qui composent cette classe sont statiques. Le choix d'utiliser des membres Static dans une classe doit cependant être bien tempéré. Par exemple, puisque la fonction init () est statique, cela signifie qu'elle peut être appelée n'importe où, n'importe quand et n'importe quel nombre de fois. Ce fait nécessite une considération supplémentaire. Dans ce cas, la toute première instruction d'une ligne dans la capture d'écran ci-dessus est une instruction if: 'i f (_init) return _init;'. C'était nécessaire lors de l'écriture de cette fonction init (). Avec cela, vous pouvez désormais appeler cette fonction autant de fois que vous le souhaitez. Quoi qu'il en soit, les services et plugins nécessaires ne sont initialisés qu'au tout premier appel. Et donc, dans une équipe de développeurs, par exemple, si l'appel à la fonction init () est effectué plusieurs fois par erreur, il n'y a pas de mal. Une autre caractéristique souhaitable d'une classe d'utilité. Vous voyez ce que je fais ici? Ce qui en fait un peu «infaillible». Droite?

Initier vos paramètres

En passant, ces paramètres étant transmis à ces variables statiques, cela signifie que vous avez plus d'options dans notre exemple. Lorsque la fonction init () est appelée, les paramètres auraient pu être spécifiés à la place. Cela permettrait à tout et tous les appels aux fonctions, Oneshot (), oneShotAt () et périodique (), d'utiliser ces paramètres si ne pas fournir explicitement leur propre. Je l'ai démontré ci-dessous. Vous pouvez voir les différences faites dans l'exemple de code si les paramètres supplémentaires de la fonction init () ont été utilisés à la place. Cela laisse juste l'appel de fonction 'oneShot' avec sa durée, son identifiant et la fonction de rappel nécessaire. Rend le code un peu plus propre. Options!

D Ne placez pas init () dans une fonction build () ! Il semblerait que la fonction init () du plugin Flutter s'appelle AndroidAlarmManager. initialize () ;, est susceptible de provoquer des effets secondaires ou des problèmes. Dans certains cas, il lancera une reconstruction (un peu comme l'appel de la fonction setState ()). C'est pourquoi ma classe utilitaire a une fonction init () distincte . Il est préférable de l'appeler au début de votre application - dans un widget FutureBuilder avec MaterialApp par exemple. Voyez par vous-même et, dans votre exemple modifié, essayez de commenter la fonction AlarmManger.init () de initSettings () et placez-la à la place juste avant sa fonction oneShot () (voir ci-dessous). Votre exemple commencera alors à rencontrer des erreurs.

android_alarm_manager.dart

Prenez votre oneShot

ok, revenons à la classe utilitaire. Dans la fonction oneShot (), les trois premières valeurs de paramètre «requises» testent la validité et sont transmises à la fonction oneShot () du plugin . Tous sauf le paramètre 'Function', callback . Au lieu de cela, cela est ajouté à un objet Map statique identifié de manière unique par l'ID entier fourni à l'aide de la commande suivante, _Callback.oneShots [id] = callback . Nous y reviendrons assez tôt. Enfin, vous remarquez qu'il y a un appel à la fonction statique, oneShot (), également trouvée dans cette classe d'assistance, _Callback . Il s'agit de la «fonction statique» nécessaire à utiliser par le plugin. Cela laisse le reste des valeurs de paramètre à prendre dans ces nombreuses variables statiques en utilisant l'opérateur de fusion nul, ?? . L'opérateur est utilisé ainsi lorsqu'un paramètre explicite n'est pas passé, les valeurs de ces variables statiques sont utilisées à la place. Tu piges? Ces variables statiques sont toutes initialisées, au fait, avec des valeurs par défaut, de sorte qu'aucune valeur nulle ne soit finalement transmise au plugin lui-même. Agréable.

alarm_manager.dart

Ne prenez aucune chance

Notez que l'appel au plugin lui-même est également inclus dans une instruction try-catch . C'est parce que c'est un programme tiers. Nous ne savons pas ce qui pourrait arriver, et comme il s'agit d'une classe utilitaire, nous ne voulons pas planter votre application, mais plutôt détecter les exceptions qui pourraient survenir.

De plus, comme toute bonne classe d'utilitaires, cette classe permet au développeur de «tester» si l'opération a réussi ou non. Sinon, toute exception qui peut s'être produite est enregistrée afin que le développeur puisse la gérer. Ceci est démontré ci-dessous dans l'exemple modifié avec l' instruction if nouvellement insérée .

android_alarm_manager.dart

Plus statique

Plus loin dans la classe utilitaire, AlarmManager . Nous voyons la fonction oneShotAt (). Encore une fois, comme toutes ces fonctions sont des fonctions statiques, certaines garanties doivent être incorporées dans le code. Dans des circonstances malheureuses, par exemple, le plugin peut ne pas être initialisé pour la première fois lorsque cette fonction onShotAt () est appelée. En d'autres termes, sa fonction init () n'a pas été appelée en premier. Cela peut arriver lorsqu'il est utilisé par le grand public. Vous pouvez voir dans la capture d'écran ci-dessous, une telle situation est testée avec la fonction assert (). C'est dans l'espoir qu'un développeur détectera une telle erreur pendant qu'il est en développement. En production, il est capturé par cette instruction if qui suit la fonction assert ().

Notez cette oneShotAt fonction () a son propre objet carte pour stocker le passé dans la fonction « de rappel », et il a sa propre fonction statique, _Callback.onShatAt (), à transmettre à propre du plugin oneShotAt fonction (). Tout cela implique, en passant, que vous pouvez appeler ces fonctions un nombre illimité de fois dans votre application en planifiant un nombre illimité d'opérations à venir. Bien sûr, chacun doit se voir attribuer sa propre valeur d'identifiant unique. Sinon, toute opération déjà planifiée sera remplacée par une nouvelle si elle utilise la même valeur d'identifiant. C'est le point lors de l'utilisation d'identifiants uniques. Droite?

Cependant, tout cela implique également que vous pouvez utiliser le même identifiant, mais entre les trois fonctions différentes, oneShot (), oneShotAt () et Periodic () séparément. N'oubliez pas qu'ils ont leurs propres objets Map et fonctions statiques. Ce fait m'a bien servi dans mon récent projet où les identifiants utilisés étaient les valeurs mêmes trouvées dans les champs primaires de la base de données résidente. Options, bébé! Aimer!

alarm_manager.dart

Un aperçu du plugin

Un rapide coup d'œil maintenant aux propres fonctions oneShot () et oneShotAt () du plugin Flutter , et vous pouvez voir que sa fonction oneShot (), en fait, ne fait que transmettre son paramètre à son homologue oneShotAt (). Notez que l'objet 'CallbackHandle' que vous voyez dans la capture d'écran ci-dessous provient de la fonction, _getCallbackHandle (), qui, à son tour, a appelé la fonction de cadre de Flutter, PluginUtilities.getCallbackHandle (callback) . Cette opération «arrache» une copie de la fonction de rappel afin d'y avoir accès et d'appeler une telle fonction dans l'Isolate s'exécutant en arrière-plan. J'y reviendrai également.

L'opération de rappel

Continuons pour l'instant et jetons un coup d'œil à la 'classe d'assistance', _Callback , dans le fichier de bibliothèque . Vous pouvez voir ci-dessous les objets Map qui s'ajoutent dans les objets de fonction de rappel sont définis dans cette classe en tant que propriétés statiques. Cette classe a également une fonction init () et elle est appelée dans la propre fonction init () de AlarmManger . C'est dans cette fonction init () qu'un «port de communication» est enregistré avec trois identificateurs de nom spécifiques. Le port est utilisé par l'isolat d'arrière-plan pour communiquer avec l'isolat de premier plan en lui passant des messages. Devinez quelles sont les valeurs de ces identificateurs de noms? Ils apparaissent dans la capture d' écran ci - dessous stockées dans les variables, _oneShot , _oneShotAt , et _periodic .

alarm_manager.dart

Comme son nom l'indique, les isolats sont des segments séparés de la mémoire
qui sont, par conception,… isolés. Il n'y a pas de partage de mémoire. Il n'y a que le passage de messages entre isolats. Le contenu d'un tel message peut être une valeur primitive (null, num, bool, double, String), une instance d'un objet SendPort , un objet List ou un objet Map avec l'une de ces valeurs primitives mentionnées en premier.

Écoutez en arrière-plan

Le port est en outre affecté à un «écouteur» pour réagir si et quand un message est reçu, dans ce cas, par l'isolat d'arrière-plan exécutant le service d'alarme. L'auditeur se présente sous la forme d'une fonction anonyme prenant un objet Map comme paramètre. Vous pouvez voir ci-dessous que l'objet Map contient une valeur entière (qui se trouve être l'id) et une chaîne stockant l'un de ces 'identificateurs de nom'. L' instruction case détermine alors quel «type» de fonction doit être déclenché. Voyez comment cela fonctionne? Bien sûr, étant une classe utilitaire, tout est inclus dans une instruction try-catch . Par exemple, nous n'avons aucune idée de ce qui se passera lorsque la fonction choisie sera exécutée. Nous voulons attraper toutes les exceptions qui peuvent survenir. Droite?

alarm_manager.dart

Envoyer un message

Alors, comment ce message est-il envoyé à l'application exécutée au premier plan? Eh bien, une fois que ces identificateurs de nom sont enregistrés ci-dessus, alors (voir ci-dessous) l'une des trois fonctions de classe d'utilitaire, AlarmManager . oneShot (), AlarmManager . oneShotAt () , et AlarmManager . Periodic (), passera les trois fonctions Static correspondantes, _Callback . onShot (), _Callback . onShotAt () et _Callback . Periodic (), directement dans le plugin Flutter. Cela permettra à Isolate d'arrière-plan de transmettre un message à l'application exécutée au premier plan Isolate. Les trois types d'appels sont répertoriés ci-dessous.

Vous voyez, ce sont ces trois fonctions statiques correspondantes, _Callback . onShot (), _Callback . onShotAt () et _Callback . periodique (), qui sont 'le pont' de l'arrière-plan Isoler au premier plan Isoler. Lorsqu'il est temps de déclencher une alarme, par exemple, le service d'alarme appellera l'une de ces trois fonctions statiques. Notez que ce n'est pas la fonction réelle définie au premier plan Isolate, mais une «copie arrachée» de celle-ci. Vous verrez un comportement contre-intuitif à cause de ce fait. Par exemple, toute variable statique de cette fonction normalement définie au premier plan Isolate sera nulle en arrière-plan Isolate. Vous pouvez tester vous-même ce phénomène.

Par exemple, dans notre exemple modifié, si j'ai appuyé sur le bouton une troisième fois, nous savons que l'objet Map, oneShots , contient cet objet Function pour exécuter et mettre à jour l'écran après cinq secondes, et c'est exactement ce qu'il fait dans la capture d'écran de la routine «Auditeur» ci-dessous. Cependant, dans le processus, cet objet Map est vide s'il est accédé en arrière-plan Isoler?! Comment est-ce possible? C'est possible car il s'agit d'une copie de l'objet Map et non de celui du premier plan Isolate. Encore une fois, les isolats ne peuvent se transmettre que des «messages». Ils ne partagent pas la mémoire.

Encore une fois, ces trois fonctions statiques sont dans la classe d'assistance, _Callback , répertoriées l'une après l'autre. Ils sont affichés dans la capture d'écran ci-dessous. Dans chacun d'eux, vous pouvez voir que le premier plan Isolate est référencé à l'aide des 'identificateurs de nom' et reçoit un objet Map de l'arrière-plan Isolate. Notez que l'opérateur d'accès aux membres conditionnels, ?. , est utilisé au cas où l'opération de recherche renvoie null. Il le fera si le nom n'existe pas par exemple. Il est peu probable que cela se produise car tout cela est du code internalisé, mais étant une classe utilitaire, nous ne prenons aucun risque. Droite?

Tout cela est à la fin du fichier de bibliothèque, et il est là où nous voyons enfin la valeur de ces « identificateurs de nom » dans les variables, _oneShot , _oneShotAt , et _periodic . Ils sont chacun nommés d'après leur type de fonction correspondant. Pas très imaginatif, mais c'est logique. Nous voyons également que ce sont des variables de haut niveau définies en dehors d'une classe ou d'une fonction de haut niveau. En fait, ce sont des variables constantes avec le mot-clé, const . Comme les variables finales, les variables const ne sont initialisées qu'une seule fois et ne peuvent pas être modifiées par la suite. Contrairement aux variables finales, elles sont définies lors de la compilation de l'application. Par conséquent, pour nos besoins ici, ils sont disponibles même pour les isolats d'arrière-plan. Agréable.

alarm_manager.dart

Si vous ne maîtrisez pas tout cela, Isolate. Ne vous inquiétez pas pour le moment. C'est à cela que sert la classe utilitaire. Contrairement à l'exemple de code original du plugin, vous n'avez pas à vous soucier de la configuration des «ports de communication» ou du moment où utiliser une fonction ou une variable statique ou de haut niveau. C'est pourquoi j'ai écrit ce cours en premier lieu - donc je n'ai pas non plus à m'inquiéter de tout cela. C'est pourquoi de telles classes d'utilité sont écrites du tout - elles peuvent donc être utilisées encore et encore par nos nombreuses applications que nous écrirons tous à l'avenir. Droite?

À votre santé.

→ Autres histoires de Greg Perry

DECODE Flutter sur YouTube