Notifiche e allarmi in Flutter

Nov 10 2020
Lavorare con il plug-in Flutter, Android_Alarm_Manager. Conosci questo! Questo plugin funziona solo per la piattaforma Android! Personalmente non conosco nessun equivalente iOS.

Lavorare con il plug-in Flutter, Android_Alarm_Manager.

Conosci questo! Questo plugin funziona solo per la piattaforma Android ! Personalmente non conosco nessun equivalente iOS. In alternativa, un mese dopo la pubblicazione di questo articolo, mi sono imbattuto in un plug-in che fornisce notifiche su entrambe le piattaforme Android e iOS. Ovviamente ho scritto un articolo e anche quello. Vedi sotto.

Notifiche in Flutter

Sappi che se vuoi usare il plugin qui descritto, devi seguire esplicitamente il suo file readme per impostare correttamente le cose. Sei AndroidManfest.xml dovrebbe almeno assomigliare a questo di seguito:

<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

In questo caso, serve per attivare gli allarmi nella tua app! Nel mio caso, ho trovato il plugin Flutter, android_alarm_manager , che soddisfaceva le esigenze di un'app recente su cui stavo lavorando e quindi ... ho creato una routine per lavorarci facilmente. Se lo desideri, prendine una copia , personalizzalo e condividi i miglioramenti che apporti. Freddo?

Ora, se hai tempo, continua a leggere quello che ho scoperto di dover fare per far funzionare questo plugin in modo quasi `` infallibile '' in modo da impostare facilmente e rapidamente un allarme o qualsiasi altra operazione da eseguire `` in background '' ad un certo punto nel tempo in futuro mentre l'app è in esecuzione. Rende la vita un po 'più facile, e questa è una buona cosa. Destra?

Solo screenshot. Fare clic su per gli elenchi.

Come sempre, preferisco usare screenshot piuttosto che sommari per mostrare il codice nei miei articoli. Li trovo più facili da lavorare e più facili da leggere. Tuttavia, puoi fare clic / toccarli per vedere il codice in un gist o in Github. Ironia della sorte, è meglio leggere questo articolo sullo sviluppo mobile sul tuo computer che sul tuo telefono. Inoltre, programmiamo principalmente sui nostri computer; non sui nostri telefoni. Per adesso.

Cominciamo.

Altre storie di Greg Perry

Il mio approccio qui è presentare prima questa classe di utilità in un esempio e dimostrare come usarla, sfruttando così il plug-in Flutter, android_alarm_manager . In effetti, userò lo stesso esempio elencato nella pagina di esempio del plugin . Tuttavia, questo esempio è stato modificato per utilizzare invece il file di libreria presentato qui. Una copia di questo esempio è disponibile come sintesi , android_alarm_manager . Dopo l'esempio, passerò attraverso parti della classe di utilità stessa descrivendo a volte cosa era necessario fare per consentire a una tale classe di essere utilizzata dalle masse inflessibili che sono il pubblico in generale.

Mantienilo statico

In questo esempio molto semplice, devi premere il pulsante visualizzato e, 5 secondi dopo, quei due zeri come mostrato nello screenshot qui sotto si trasformeranno in uno. Wheeee! Ciò che è veramente interessante, ovviamente, è quello che c'è sotto il cofano nel codice.

Nota, ho cambiato il codice dall'originale per accogliere le operazioni asincrone che devono essere eseguite prima che l'app possa effettivamente iniziare a funzionare. Ho utilizzato un widget FutureBuilder per farlo. In tal modo, ho definito una nuova funzione chiamata, initSettings () , per inizializzare la routine "Preferenze condivise" utilizzata per "ricordare" il numero totale di allarmi attivati ​​come mostrato nello screenshot qui sopra.

android_alarm_manager.dart

Puoi anche vedere nella funzione initSettings () mostrata di seguito, che imposta il "conteggio totale" a zero se questa è la prima volta che l'app viene eseguita. È all'inizio, tuttavia, che la routine della libreria, AlarmManager , viene inizializzata per impostare il particolare "Servizio di allarme" necessario per eseguire le notifiche su un telefono Android.

android_alarm_manager.dart

Andiamo più in basso nel codice di esempio e vediamo cosa compone quel piccolo pulsante. Nota, la routine della libreria utilizza gli stessi nomi per i parametri e le funzioni che compongono il plug-in Flutter sottolineato, Android_Alarm_Manager . Meglio essere coerenti con queste cose. Inoltre, proprio come la funzione oneShot () del plugin , la versione di questa libreria, una volta chiamata, "attenderà" per la durata specificata prima di attivare la routine di callback specificata. Nel caso di questo esempio modificato, la routine di callback è una funzione anonima che verrà eseguita dopo una durata di 5 secondi. Nello screenshot qui sotto, infatti, l'app è stata riavviata premendo nuovamente il pulsante indicando, grazie alle Preferenze Condivise, che il pulsante è stato premuto due volte da quando era stato eseguito per la prima volta. Wheeee.

android_alarm_manager.dart

Uno sguardo più da vicino a quella funzione anonima di seguito, e vediamo che viene passato un singolo parametro di tipo, intero. Puoi immaginare che sia lo stesso valore id passato al secondo parametro come numero casuale utilizzando le funzioni Dart, Random (). NextInt (pow (2,31)) . Ora perché preoccuparsi di passare quel valore quando è già passato nel parametro accanto ad esso? Ci arriveremo.

Per ora, puoi vedere ulteriormente di seguito che la funzione di callback, a sua volta, chiama la funzione, _incrementCounter (). Lì, l'attuale "conteggio totale" delle pressioni dei pulsanti viene recuperato dalla routine "Preferenze condivise". Viene quindi aggiornato da uno per tenere conto della pressione di questo pulsante corrente e salvato di nuovo nelle Preferenze condivise. La schermata dell'app viene quindi aggiornata con la variabile incrementata, _counter , utilizzando la funzione setState (). Hai seguito tutto ciò finora?

android_alarm_manager.dart

Mantenerlo statico

A differenza dell'esempio originale (vedere di seguito), questo esempio può utilizzare una funzione anonima e non è necessario utilizzare in modo specifico una funzione statica. Ovviamente, puoi usare anche una funzione statica in questo esempio, purché accetti ancora quel valore intero solitario. Tuttavia, nell'esempio originale, deve essere una funzione statica o una funzione di alto livello definita al di fuori di qualsiasi classe in quel file Dart della libreria: l'una o l'altra, vedete, è un requisito quando si lavora direttamente con il plug-in Flutter, Android_Alarm_Manager .

Tuttavia, se hai seguito i miei articoli, sai che mi piacciono le opzioni. Si tratta solo di avere opzioni con me. Ho scritto questa classe di utilità per essere più accomodante, ad esempio per consentire funzioni anonime. Con una tale disposizione, quindi, passare quel valore intero id come parametro consente effettivamente alla funzione di essere una funzione statica, una funzione di alto livello o una funzione anonima. Opzioni! Di nuovo, gli screenshot dell'esempio originale seguono di seguito:

Passiamo alla classe di utilità, AlarmManager , stessa. Anche in questo caso, è progettato per funzionare con il plug-in. E ancora, molti parametri sono gli stessi parametri utilizzati dal plug-in e quindi vengono passati a quel plug-in, ma non prima di alcuni test approfonditi dei parametri per i valori validi. Un altro tratto necessario di tali classi di utilità. Fa tutto il lavoro, quindi non devi. Destra?

Nello screenshot qui sotto, è la prima parte di questa classe di utilità. Nella sua funzione statica, init (), vediamo che il plugin è effettivamente inizializzato. Nota, qualsiasi errore sfortunato che potrebbe verificarsi nel tentativo di inizializzazione verrà catturato nell'istruzione try-catch . Anche le classi di utilità devono farlo. Successivamente, nella funzione init (), c'è la classe 'helper', _Callback , che viene chiamata per inizializzare i mezzi necessari all'app per comunicare con il separato Isolate utilizzato dal servizio di allarme in background.

Infine, puoi vedere di seguito che tutti i valori dei parametri non nulli sono assegnati alle proprietà specifiche e anche statiche che compongono la classe di utilità, AlarmManager . Un sacco di statica in corso qui non c'è.

alarm_manager.dart

Troverai che molte delle proprietà e delle funzioni che compongono questa classe sono statiche. La scelta di utilizzare membri statici in una classe dovrebbe tuttavia essere moderata. Ad esempio, poiché la funzione init () è Static, ciò significa che può essere chiamata ovunque, in qualsiasi momento e in qualsiasi numero di volte. Questo fatto richiede qualche considerazione aggiuntiva. In questo caso, la primissima istruzione di una riga nello screenshot sopra è un'istruzione if: 'i f (_init) return _init;'. Era necessario durante la scrittura di questa funzione init (). Con ciò, ora puoi chiamare quella funzione tutte le volte che vuoi. Indipendentemente da ciò, i servizi e i plug-in necessari vengono inizializzati solo alla prima chiamata. E così, in un team di sviluppatori, ad esempio, se la chiamata alla funzione init () viene erroneamente eseguita più di una volta, non c'è nulla di male. Un'altra caratteristica desiderabile di una classe di utilità. Vedi cosa ci faccio qui? Rendendolo una sorta di "infallibile". Destra?

Inizializza le tue impostazioni

A proposito, con questi parametri passati a quelle variabili statiche, ciò significa che hai più opzioni nel nostro esempio. Quando viene chiamata la funzione init (), le impostazioni avrebbero potuto essere specificate in quel momento. Ciò consentirebbe a tutte le chiamate successive alle funzioni, oneShot (), oneShotAt () e periodic (), di utilizzare tali impostazioni se non fornendo esplicitamente le proprie. L'ho dimostrato di seguito. Puoi vedere le differenze fatte nel codice di esempio se sono stati usati i parametri aggiuntivi nella funzione init (). Questo lascia solo la chiamata alla funzione "oneShot" con la sua durata, il suo id e la funzione di callback necessaria. Rende il codice un po 'più pulito. Opzioni!

D Non inserire init () in una funzione build () ! Sembrerebbe la funzione init () del plugin Flutter chiamata AndroidAlarmManager. initialize () ;, è incline a causare effetti collaterali o problemi. In alcuni casi, avvierà una ricostruzione (molto simile alla chiamata della funzione setState ()). Ecco perché la mia classe di utilità ha una funzione init () separata . È preferibile che venga chiamato vicino all'inizio della tua app, ad esempio in un widget FutureBuilder con MaterialApp. Guarda tu stesso e, nel tuo esempio modificato, prova a commentare la funzione AlarmManger.init () da initSettings () e posizionala subito prima della sua funzione oneShot () (vedi sotto). Il tuo esempio inizierà quindi a incontrare errori.

android_alarm_manager.dart

Prendi il tuo oneShot

ok, torniamo alla classe di utilità. Nella funzione oneShot (), i primi tre valori dei parametri "obbligatori" stanno verificando la validità e vengono passati alla funzione oneShot () del plug-in . Tutti tranne il parametro "Funzione", callback . Invece, viene aggiunto a un oggetto Map statico identificato in modo univoco dall'id intero fornito utilizzando il seguente comando, _Callback.oneShots [id] = callback . Ci torneremo abbastanza presto. Infine, noti che c'è una chiamata alla funzione statica, oneShot (), che si trova anche in quella classe helper, _Callback . È la "funzione statica" necessaria per essere utilizzata dal plugin. Ciò lascia che il resto dei valori dei parametri includa quelle molte variabili statiche utilizzando l'operatore di coalescenza null, ?? . L'operatore viene utilizzato in modo che quando non viene passato un parametro esplicito, vengono utilizzati invece i valori in quelle variabili statiche. Prendilo? Queste variabili statiche sono tutte inizializzate, tra l'altro, con valori predefiniti, quindi non ci sono valori nulli eventualmente passati al plugin stesso. Bello.

alarm_manager.dart

Non correre rischi

Nota, anche la chiamata al plugin stesso è racchiusa in un'istruzione try-catch . Questo perché è un programma di terze parti. Non sappiamo cosa potrebbe accadere e, poiché si tratta di una classe di utilità, non vogliamo che la tua app si arresti in modo anomalo, ma invece rilevare eventuali eccezioni che potrebbero verificarsi.

Inoltre, come ogni buona classe di utilità, questa classe ha i mezzi per lo sviluppatore "per verificare" se l'operazione ha avuto successo o meno. In caso contrario, qualsiasi eccezione che potrebbe essersi verificata viene registrata in modo che lo sviluppatore possa gestirla. Ciò è dimostrato di seguito nell'esempio modificato con l' istruzione if appena inserita .

android_alarm_manager.dart

Più statico

Più avanti nella classe di utilità, AlarmManager . Vediamo la funzione oneShotAt (). Ancora una volta, poiché tutte queste funzioni sono funzioni statiche, è necessario incorporare alcune protezioni nel codice. In circostanze sfortunate, ad esempio, il plugin potrebbe non essere inizializzato prima quando viene chiamata questa funzione onShotAt (). In altre parole, la sua funzione init () non è stata chiamata per prima. Potrebbe accadere se utilizzato dal grande pubblico. Puoi vedere nello screenshot qui sotto, una situazione del genere viene testata con la funzione assert (). Questo è nella speranza che uno sviluppatore cogli questo errore mentre è in fase di sviluppo. Nella produzione, è catturato dall'istruzione if che segue la funzione assert ().

Nota, questo oneShotAt function () dispone di un proprio oggetto Mappa di immagazzinare il passato in funzione di 'callback', ed ha una sua funzione statica, _Callback.onShatAt (), da passare al proprio del plugin oneShotAt function (). Tutto ciò implica, a proposito, che puoi chiamare queste funzioni un numero qualsiasi di volte nella tua app pianificando un numero qualsiasi di operazioni da eseguire in futuro. Ovviamente, a ciascuno deve essere assegnato il proprio valore ID univoco, ricorda. In caso contrario, qualsiasi operazione già pianificata verrà sovrascritta con una nuova se capita di utilizzare lo stesso valore id. Questo è il punto quando si usano ID univoci. Destra?

Tuttavia, tutto ciò implica anche che puoi usare lo stesso id, ma tra le tre diverse funzioni, oneShot (), oneShotAt () e periodic () separatamente. Ricorda, hanno i loro oggetti mappa separati e funzioni statiche. Questo fatto mi è servito bene nel mio recente progetto in cui gli ID utilizzati erano gli stessi valori trovati nei campi primari del database residente. Opzioni, piccola! Lo adoro!

alarm_manager.dart

Un plug-in Peek

Una rapida occhiata alle funzioni oneShot () e oneShotAt () del plug-in Flutter , e puoi vedere che la sua funzione oneShot (), infatti, passa semplicemente il suo parametro alla sua controparte oneShotAt (). Nota, l'oggetto 'CallbackHandle' che vedi nello screenshot qui sotto proviene dalla funzione, _getCallbackHandle (), che, a sua volta, chiama la funzione framework di Flutter, PluginUtilities.getCallbackHandle (callback) . Questa operazione "strappa" una copia della funzione di callback in modo da accedervi e chiamare tale funzione nell'Isolate in esecuzione in background. Tornerò anche su questo.

L'operazione di richiamata

Continuiamo per ora e diamo un'occhiata alla 'classe helper,' _Callback , nel file della libreria . Di seguito è possibile vedere gli oggetti mappa che vengono aggiunti negli oggetti funzione Callback sono definiti in questa classe come proprietà statiche. Questa classe ha anche una funzione init () e viene chiamata nella funzione init () di AlarmManger . È in questa funzione init () che viene registrata una "porta di comunicazione" con tre identificatori di nome specifici. La porta viene utilizzata dall'Isolate in background per comunicare con l'Isolate in primo piano passandovi dei messaggi. Indovina quali sono i valori di questi identificatori di nomi? Essi appaiono nello screenshot qui sotto memorizzati nelle variabili, _oneShot , _oneShotAt , e _periodic .

alarm_manager.dart

Come suggerisce il nome, gli isolati sono segmenti separati di memoria
che sono per progettazione, beh, ... isolati. Non c'è condivisione della memoria. C'è solo il passaggio di messaggi tra gli isolati. Il contenuto di un messaggio di questo tipo può essere un valore primitivo (null, num, bool, double, String), un'istanza di un oggetto SendPort , un oggetto List o un oggetto Map con uno di questi valori primitivi menzionati per primo.

Ascolta in sottofondo

Alla porta viene inoltre assegnato un "listener" per reagire se e quando un messaggio viene ricevuto, in questo caso, dallo sfondo Isolate che esegue il servizio di allarme. L'ascoltatore ha la forma di una funzione anonima che prende un oggetto Map come parametro. Puoi vedere di seguito che l'oggetto Map contiene un valore intero (che sembra essere l'id) e una stringa che memorizza uno di quegli "identificatori di nome". L' istruzione case determina quindi quale "tipo" di funzione deve essere attivata. Vedi come funziona? Ovviamente, essendo una classe di utilità, è tutto racchiuso in un'istruzione try-catch . Ad esempio, non abbiamo idea di cosa accadrà quando viene eseguita la funzione scelta. Vogliamo rilevare tutte le eccezioni che possono verificarsi. Destra?

alarm_manager.dart

Mandare un messaggio

Allora come viene inviato quel messaggio all'app in esecuzione in primo piano? Bene, una volta che questi identificatori di nome sono stati registrati sopra, allora (vedi sotto) una delle tre funzioni di classe di utilità, AlarmManager . oneShot (), AlarmManager . oneShotAt () , e AlarmManager . periodic (), passerà le tre corrispondenti funzioni statiche, _Callback . onShot (), _Callback . onShotAt () e _Callback . periodic (), direttamente al plugin Flutter. In questo modo, l'isolamento in background passerà di nuovo un messaggio all'app in esecuzione in primo piano. Tutti e tre i tipi di chiamate sono elencati di seguito.

Vedete, sono queste tre funzioni statiche corrispondenti, _Callback . onShot (), _Callback . onShotAt () e _Callback . periodic (), che sono 'il ponte' dallo sfondo Isolate al primo piano Isolate. Quando è il momento di attivare un allarme, ad esempio, il servizio di allarme chiamerà una di queste tre funzioni statiche. Nota, come accade, non è la funzione effettiva definita in primo piano Isolate, ma una sua "copia strappata". Vedrai un comportamento controintuitivo a causa di questo fatto. Ad esempio, qualsiasi variabile statica in quella funzione normalmente definita in primo piano Isolate sarà nulla in background Isolate. Puoi testare questo fenomeno da solo.

Ad esempio, nel nostro esempio modificato, se ho premuto il pulsante una terza volta, sappiamo che l'oggetto Map, oneShots , contiene quell'oggetto Funzione per eseguire e aggiornare lo schermo dopo cinque secondi, e lo fa proprio nello screenshot della routine "Listener" di seguito. Tuttavia, nel processo, quell'oggetto Map è vuoto se vi si accede in background Isolare ?! Come è possibile? È possibile perché è una copia dell'oggetto Map e non quello in primo piano Isolate. Anche in questo caso, gli isolati possono solo scambiarsi "messaggi". Non condividono la memoria.

Ancora una volta, queste tre funzioni statiche sono nella classe helper, _Callback , elencate una dopo l'altra. Sono visualizzati nello screenshot qui sotto. In ciascuno, puoi vedere che l'isolamento in primo piano è referenziato usando gli "identificatori di nome" e viene passato un oggetto mappa dallo sfondo isolato. Nota, l'operatore di accesso condizionale ai membri, ?. , viene utilizzato nel caso in cui l'operazione di ricerca restituisca null. Lo farà se il nome non esiste, ad esempio. È improbabile che accada mai poiché questo è tutto codice interiorizzato, ma essendo una classe di utilità, non corriamo rischi. Destra?

Tutto questo è alla fine del file di libreria, ed è qui dove abbiamo finalmente vedere il valore di quei 'identificatori name' nelle variabili, _oneShot , _oneShotAt , e _periodic . Ciascuno di essi prende il nome dal tipo di funzione corrispondente. Non molto fantasioso, ma ha senso. Vediamo anche che sono variabili di alto livello definite al di fuori di una classe o di una funzione di alto livello. In effetti, sono variabili costanti con la parola chiave const . Come le variabili finali, le variabili const vengono inizializzate solo una volta e non possono essere modificate. A differenza delle variabili finali, vengono definite quando l'app viene compilata. Quindi, per le nostre esigenze qui, sono disponibili anche per gli isolati in background. Bello.

alarm_manager.dart

Se non hai un controllo su tutta questa roba Isolare. Non preoccuparti adesso. Ecco a cosa serve la classe di utilità. A differenza del codice di esempio del plug-in originale, non devi preoccuparti della configurazione delle "porte di comunicazione" o di quando utilizzare una funzione o una variabile Statica o di alto livello. Ecco perché ho scritto questa lezione in primo luogo, quindi non devo nemmeno preoccuparmi di tutte quelle cose. Ecco perché tali classi di utilità sono scritte, in modo che possano essere utilizzate più e più volte dalle nostre numerose app che scriveremo tutti in futuro. Destra?

Saluti.

→ Altre storie di Greg Perry

DECODIFICA Flutter su YouTube