Notificação e alarmes em vibração

Nov 10 2020
Trabalhando com o plug-in Flutter, Android_Alarm_Manager. Saiba isso! Este plugin funciona apenas para a plataforma Android! Eu pessoalmente não conheço nenhum equivalente do iOS.

Trabalhando com o plug-in Flutter, Android_Alarm_Manager.

Saiba isso! Este plugin funciona apenas para a plataforma Android ! Eu pessoalmente não conheço nenhum equivalente do iOS. Como alternativa, um mês após a publicação deste artigo, encontrei um plug-in que fornece notificações nas plataformas Android e iOS. Eu, é claro, escrevi um artigo e isso também. Ver abaixo.

Notificações no Flutter

Saiba que se você deseja usar o plugin descrito aqui, você deve seguir seu arquivo leia-me explicitamente para configurar as coisas corretamente. Seu AndroidManfest.xml deve, pelo menos, ter a seguinte aparência:

<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

Neste caso, é para disparar alarmes em seu app! No meu caso, descobri que o plugin Flutter, android_alarm_manager , atendia às necessidades de um aplicativo recente em que estava trabalhando e então… Criei uma rotina para trabalhar facilmente com ele. Se você quiser, faça uma cópia , torne-o seu e compartilhe todas as melhorias que você fizer. Legal?

Agora, se você tiver tempo, continue lendo o que descobri que precisava fazer para que este plugin funcionasse quase 'à prova de falhas' para definir um alarme de forma fácil e rápida ou qualquer outra operação a ser realizada 'em segundo plano' em algum ponto no futuro, enquanto o aplicativo está em execução. Isso torna a vida um pouco mais fácil e isso é uma coisa boa. Direito?

Apenas screenshots. Clique em For Gists.

Como sempre, prefiro usar capturas de tela em vez de gists para mostrar o código em meus artigos. Acho que são mais fáceis de trabalhar e de ler. No entanto, você pode clicar / tocar neles para ver o código em uma essência ou no Github. Ironicamente, é melhor ler este artigo sobre desenvolvimento móvel em seu computador do que em seu telefone. Além disso, programamos principalmente em nossos computadores; não em nossos telefones. Por enquanto.

Vamos começar.

Outras histórias de Greg Perry

Minha abordagem aqui é apresentar primeiro essa classe de utilitário em um exemplo e demonstrar como usá-la - aproveitando assim o plug-in Flutter, android_alarm_manager . Na verdade, usarei o mesmo exemplo listado na própria página de exemplo do plug-in . No entanto, este exemplo foi modificado para utilizar o arquivo de biblioteca apresentado aqui. Uma cópia deste exemplo está disponível para você como a essência, android_alarm_manager . Após o exemplo, examinarei partes da própria classe de utilitários, descrevendo às vezes o que precisava ser feito para permitir que tal classe fosse usada pelas massas inflexíveis que são o público em geral.

Mantenha-o estático

Neste exemplo muito simples, você deve pressionar o botão exibido e, 5 segundos depois, esses dois zeros conforme ilustrado na imagem abaixo se transformarão em uns. Wheeee! O que é realmente interessante, é claro, é o que está por trás do código.

Observe, eu alterei o código original para acomodar as operações assíncronas que devem ser executadas antes que o aplicativo possa realmente começar a ser executado. Usei um widget FutureBuilder para fazer isso. Ao fazer isso, eu defini uma nova função chamada, initSettings () , para inicializar a rotina de 'Preferências compartilhadas' usada para 'lembrar' o número total de alarmes disparados conforme visto na captura de tela acima.

android_alarm_manager.dart

Você também pode ver na função initSettings () exibida abaixo, ela define a 'contagem total' como zero se esta for a primeira vez que o aplicativo foi executado. É no início, entretanto, que a rotina da biblioteca, AlarmManager , é inicializada para configurar o 'Serviço de alarme' específico necessário para executar notificações em um telefone Android.

android_alarm_manager.dart

Vamos prosseguir no código de exemplo e ver o que constitui esse pequeno botão. Observe que a rotina da biblioteca usa os mesmos nomes para os parâmetros e funções que compõem o plug-in do Flutter sublinhado, Android_Alarm_Manager . Melhor ser consistente com essas coisas. Além disso, assim como a função oneShot () do próprio plugin , a versão desta biblioteca, uma vez chamada, 'aguardará' a duração especificada antes de disparar a rotina de retorno de chamada especificada. No caso deste exemplo modificado, a rotina de retorno de chamada é uma função anônima que será executada após 5 segundos. Na imagem abaixo, o aplicativo foi, de fato, reiniciado com o botão pressionado novamente indicando, graças às Preferências compartilhadas, que o botão foi pressionado duas vezes desde que foi executado pela primeira vez. Wheeee.

android_alarm_manager.dart

Olhando mais de perto a função anônima abaixo, vemos que um único parâmetro do tipo é passado, inteiro. Você pode adivinhar que é o mesmo valor de id passado para o segundo parâmetro como um número aleatório usando as funções do Dart, Random (). NextInt (pow (2,31)) . Agora, por que se preocupar em passar esse valor quando ele já foi passado para o parâmetro ao lado dele? Nós vamos chegar a isso.

Por enquanto, você pode ver abaixo que a função de retorno de chamada, por sua vez, chama a função _incrementCounter (). Lá, a 'contagem total' atual de pressionamentos de botão é recuperada da rotina 'Preferências compartilhadas'. Ele é então atualizado por um para contabilizar este pressionamento de botão atual e salvo de volta nas Preferências compartilhadas. A tela do aplicativo é então atualizada com a variável incrementada, _counter , usando a função setState (). Você acompanhou tudo isso até agora?

android_alarm_manager.dart

Mantendo-o estático

Ao contrário do exemplo original (veja abaixo), este exemplo tem permissão para usar uma função anônima e não é necessário para usar especificamente uma função estática. Obviamente, você também pode usar uma função estática neste exemplo - contanto que ela ainda aceite aquele valor inteiro solitário. No entanto, no exemplo original, deve ser uma função estática ou uma função de alto nível definida fora de qualquer classe no arquivo Dart da biblioteca - qualquer um, você vê, é um requisito ao trabalhar diretamente com o plug-in Flutter, Android_Alarm_Manager .

No entanto, se você tem seguido meus artigos, sabe que gosto de opções. É tudo uma questão de ter opções comigo. Escrevi esta classe de utilitário para ser mais flexível - para permitir funções anônimas, por exemplo. Com tal arranjo, portanto, passar esse valor inteiro id como um parâmetro realmente permite que a função seja uma função estática, uma função de alto nível ou uma função anônima. Opções! Novamente, as capturas de tela do exemplo original seguem abaixo:

Vamos nos voltar para a própria classe de utilitário, AlarmManager . Novamente, ele foi projetado para funcionar com o plugin. E, novamente, seus muitos parâmetros são exatamente os mesmos parâmetros usados ​​pelo plug-in e, portanto, são passados ​​para esse plug-in - mas não antes de alguns testes extensivos de parâmetros para valores válidos. Outra característica necessária dessas classes de utilitários. Ele faz todo o trabalho para que você não precise fazer isso. Direito?

Na captura de tela abaixo, está a primeira parte desta classe de utilitário. Em sua função estática, init (), vemos que o plugin foi realmente inicializado. Observe que quaisquer erros infelizes que possam ocorrer na tentativa de inicialização serão capturados na instrução try-catch . As classes utilitárias também precisam fazer isso. Em seguida, na função init (), há a classe 'auxiliar', _Callback , que é chamada para inicializar os meios necessários para o aplicativo se comunicar com o Isolate separado usado pelo Serviço de Alarme em segundo plano.

Por último, você pode ver abaixo que todo e qualquer valor de parâmetro diferente de nulo é atribuído às propriedades específicas e também estáticas que compõem a classe do utilitário, AlarmManager . Muita estática acontecendo por aqui não está lá.

alarm_manager.dart

Você descobrirá que muitas das propriedades e funções que compõem essa classe são estáticas. A escolha de usar membros estáticos em uma classe deve ser bem tolerada, no entanto. Por exemplo, como a função init () é estática, isso significa que ela pode ser chamada em qualquer lugar, a qualquer hora e qualquer número de vezes. Esse fato requer algumas considerações adicionais. Neste caso, a primeira instrução de uma linha na imagem acima é uma instrução if: 'i f (_init) return _init;'. Foi necessário ao escrever esta função init (). Com isso, agora você pode chamar essa função quantas vezes quiser. Independentemente disso, os serviços e plug-ins necessários são inicializados apenas na primeira chamada. E assim, em uma equipe de desenvolvedores, por exemplo, se a chamada para a função init () for feita por engano mais de uma vez, não haverá dano. Outra característica desejável de uma classe de utilidade. Vê o que estou fazendo aqui? Tornando-o meio 'à prova de falhas'. Direito?

Inicie suas configurações

A propósito, com esses parâmetros sendo passados ​​para essas variáveis ​​estáticas, isso significa que você tem mais opções em nosso exemplo. Quando a função init () é chamada, as configurações poderiam ter sido especificadas naquele momento. Isso permitiria que toda e qualquer chamada subsequente às funções oneShot (), oneShotAt () e periodic () usassem essas configurações, se não fornecerem explicitamente as suas próprias. Eu demonstrei isso abaixo. Você pode ver as diferenças feitas no código de exemplo se os parâmetros adicionais na função init () foram usados. Isso deixa apenas a chamada de função 'oneShot' com sua duração, seu id e a função de retorno de chamada necessária. Torna o código um pouco mais limpo. Opções!

D Não coloque init () em uma função build () ! Aparentemente, a função init () do plugin Flutter é chamada, AndroidAlarmManager. initialize () ;, está sujeito a causar efeitos colaterais ou problemas. Em alguns casos, ele iniciará uma reconstrução (semelhante à chamada da função setState ()). É por isso que minha classe de utilitário tem uma função init () separada . É preferível que seja chamado próximo ao início de seu aplicativo - em um widget FutureBuilder com MaterialApp, por exemplo. Veja você mesmo e, em seu exemplo modificado, tente comentar a função AlarmManger.init () de initSettings () e coloque-a antes de sua função oneShot () (veja abaixo). Seu exemplo então começará a encontrar erros.

android_alarm_manager.dart

Take Your OneShot

ok, de volta à aula de utilitários. Na função oneShot (), os três primeiros valores de parâmetro 'obrigatórios' são testados para validade e repassados ​​para a função oneShot () do próprio plugin . Todos, exceto o parâmetro 'Função', retorno de chamada . Em vez disso, isso é adicionado a um objeto Map estático identificado exclusivamente pelo ID inteiro fornecido usando o seguinte comando, _Callback.oneShots [id] = callback . Voltaremos a isso em breve. Por último, você percebe que há uma chamada para a função estática, oneShot (), também encontrada nessa classe auxiliar, _Callback . É a 'função estática' necessária para ser usada pelo plugin. Isso deixa o restante dos valores dos parâmetros para incluir essas muitas variáveis ​​estáticas usando o operador de coalescência nula, ?? . O operador é usado de forma que, quando um parâmetro explícito não é passado, os valores dessas variáveis ​​estáticas são usados. Pegue? Essas variáveis ​​estáticas são todas inicializadas, a propósito, com valores padrão, então não há valores nulos eventualmente passados ​​para o próprio plugin. Agradável.

alarm_manager.dart

Não arrisque

Observe que a chamada ao próprio plug-in também está incluída em uma instrução try-catch . Isso porque é um programa de terceiros. Não sabemos o que pode acontecer e, como se trata de uma classe de utilitário, não queremos travar seu aplicativo, mas sim capturar todas as exceções que possam ocorrer.

Além disso, como qualquer boa classe de utilitário, essa classe tem os meios para o desenvolvedor 'testar' se a operação foi bem-sucedida ou não. Caso contrário, qualquer exceção que possa ter ocorrido é registrada para que o desenvolvedor possa tratá-la. Isso é demonstrado abaixo no exemplo modificado com a instrução if recém-inserida .

android_alarm_manager.dart

Mais estático

Mais adiante na classe do utilitário, AlarmManager . Vemos a função oneShotAt (). Novamente, como todas essas funções são funções estáticas, algumas proteções precisam ser incorporadas ao código. Em circunstâncias infelizes, por exemplo, o plug-in pode não ser inicializado primeiro quando a função onShotAt () é chamada. Em outras palavras, sua função init () não foi chamada primeiro. Isso pode acontecer quando usado pelo público em geral. Você pode ver na captura de tela abaixo, tal situação é testada com a função assert (). Esperamos que um desenvolvedor detecte esse erro enquanto estiver em desenvolvimento. Na produção, é capturado por aquela instrução if que segue a função assert ().

Note, este oneShotAt function () tem seu próprio objeto Mapa para armazenar o passado em função de 'retorno', e tem a sua própria função estática, _Callback.onShatAt (), a ser passado ao próprio do plugin oneShotAt function (). A propósito, isso tudo implica que você pode chamar essas funções qualquer número de vezes em seu aplicativo, agendando qualquer número de operações para ocorrer no futuro. Claro, cada um deve receber seu próprio valor de id exclusivo, lembre-se. Caso contrário, qualquer operação já agendada será substituída por uma nova se acontecer de usar o mesmo valor de id. Esse é o ponto ao usar IDs exclusivos. Direito?

No entanto, tudo isso também implica que você pode usar o mesmo id, mas entre as três funções diferentes, oneShot (), oneShotAt () e periódico () separadamente. Lembre-se de que eles têm seus próprios objetos Map e funções estáticas separados. Este fato me serviu bem em meu projeto recente, onde os ids usados ​​eram os próprios valores encontrados nos campos primários do banco de dados residente. Opções, baby! Adoro!

alarm_manager.dart

Uma espiada no plugin

Uma olhada rápida agora nas funções oneShot () e oneShotAt () do plug-in Flutter e você pode ver que sua função oneShot (), na verdade, apenas passa seu parâmetro junto com sua contraparte oneShotAt (). Observe que o objeto 'CallbackHandle' que você vê na captura de tela abaixo vem da função _getCallbackHandle (), que, por sua vez, chamou a função de estrutura do Flutter, PluginUtilities.getCallbackHandle (callback) . Essa operação 'corta' uma cópia da função de retorno de chamada para ter acesso a ela e chamar tal função no Isolate em execução em segundo plano. Voltarei a isso também.

A operação de retorno de chamada

Vamos continuar por agora e dar uma olhada na 'classe auxiliar,' _Callback , no arquivo de biblioteca . Você pode ver abaixo que os objetos Map que estão sendo adicionados aos objetos da função Callback são definidos nesta classe como propriedades estáticas. Essa classe também tem uma função init () e é chamada na função init () do próprio AlarmManger . É nesta função init () onde uma 'porta de comunicação' é registrada com três identificadores de nome específicos. A porta é usada pelo Isolate de segundo plano para se comunicar com o Isolate de primeiro plano, passando mensagens para ele. Adivinhe quais são os valores desses identificadores de nomes? Eles aparecem na imagem abaixo armazenados nas variáveis, _oneShot , _oneShotAt , e _periodic .

alarm_manager.dart

Como o nome indica, isolados são segmentos separados de memória
que são por design, bem, ... isolados. Não há compartilhamento de memória. Existe apenas a passagem de mensagens entre os Isolados. O conteúdo de tal mensagem pode ser um valor primitivo (null, num, bool, double, String), uma instância de um objeto SendPort , um objeto List ou um objeto Map com qualquer um dos valores primitivos mencionados primeiro.

Ouça em segundo plano

A porta recebe ainda um 'ouvinte' para reagir se e quando uma mensagem for recebida, neste caso, pelo Isolate em segundo plano executando o serviço de alarme. O ouvinte está na forma de uma função anônima, tendo um objeto Map como seu parâmetro. Você pode ver abaixo que o objeto Map contém um valor inteiro (que por acaso é o id) e uma String armazenando um desses 'identificadores de nome'. A instrução case determina então qual 'tipo' de função deve ser acionada. Veja como isso funciona? Claro, sendo uma classe de utilitário, está tudo incluído em uma instrução try-catch . Por exemplo, não temos ideia do que acontecerá quando a função escolhida for executada. Queremos capturar quaisquer exceções que possam ocorrer. Direito?

alarm_manager.dart

Envie uma mensagem

Então, como essa mensagem é enviada ao aplicativo em execução em primeiro plano? Bem, uma vez que esses identificadores de nome são registrados acima, então (veja abaixo) qualquer uma das três funções de classe de utilitário, AlarmManager . oneShot (), AlarmManager . oneShotAt () , e AlarmManager . periodic (), passará as três funções estáticas correspondentes, _Callback . onShot (), _Callback . onShotAt () e _Callback . periodic (), diretamente para o plugin Flutter. Isso permitirá que o Isolate em segundo plano transmita uma mensagem de volta ao aplicativo em execução no Isolate em primeiro plano. Todos os três tipos de chamadas estão listados abaixo.

Veja, são essas três funções estáticas correspondentes, _Callback . onShot (), _Callback . onShotAt () e _Callback . periódico (), que são 'a ponte' do fundo Isolar para o primeiro plano Isolar. Quando chegar a hora de disparar um alarme, por exemplo, o serviço de alarme chamará uma dessas três funções estáticas. Observe que, por acaso, não é a função real definida em Isolate em primeiro plano, mas uma 'cópia arrancada' dela. Você verá algum comportamento contra-intuitivo por causa desse fato. Por exemplo, qualquer variável estática nessa função normalmente definida em Isolate em primeiro plano será nula em Isolate em segundo plano. Você mesmo pode testar esse fenômeno.

Por exemplo, em nosso exemplo modificado, se eu pressionar o botão uma terceira vez, saberemos que o objeto Map, oneShots , tem aquele objeto Function nele para executar e atualizar a tela após cinco segundos, e faz exatamente isso na captura de tela da rotina 'Listener' abaixo. No entanto, no processo, esse objeto Map fica vazio se acessado em segundo plano Isolar ?! Como isso é possível? É possível porque é uma cópia do objeto Map e não aquele em Isolate em primeiro plano. Novamente, os isolados só podem passar 'mensagens' uns para os outros. Eles não compartilham memória.

Novamente, essas três funções estáticas estão na classe auxiliar, _Callback , listadas uma após a outra. Eles são exibidos na imagem abaixo. Em cada um, você pode ver que o Isolate de primeiro plano é referenciado usando os 'identificadores de nome' e é passado um objeto Map do Isolate de fundo. Observe, o operador de acesso condicional de membro, ?. , é usado caso a operação de pesquisa retorne nulo. Isso acontecerá se o nome não existir, por exemplo. Não é provável que aconteça, pois tudo isso é código internalizado, mas sendo uma classe de utilitário, não corremos riscos. Direito?

Tudo isso está no final do arquivo de biblioteca, e é aqui onde nós finalmente ver o valor desses identificadores de nome 'nas variáveis, _oneShot , _oneShotAt , e _periodic . Cada um tem o nome de seu tipo de função correspondente. Não é muito imaginativo, mas faz sentido. Também vemos que são variáveis ​​de alto nível definidas fora de uma classe ou função de alto nível. Na verdade, eles são variáveis ​​constantes com a palavra-chave, const . Como as variáveis ​​finais, as variáveis ​​const são inicializadas apenas uma vez e não podem ser alteradas. Ao contrário das variáveis ​​finais, elas são definidas quando o aplicativo é compilado. Portanto, para nossas necessidades aqui, eles estão disponíveis até mesmo para Isolados de fundo. Agradável.

alarm_manager.dart

Se você não tem controle sobre todas essas coisas do Isolate. Não se preocupe com isso agora. É para isso que serve a classe de utilitário. Ao contrário do código de exemplo do plugin original, você não precisa se preocupar com a configuração de 'portas de comunicação' ou quando usar uma função ou variável estática ou de alto nível. É por isso que escrevi esta aula em primeiro lugar - então não preciso me preocupar com todas essas coisas também. É por isso que essas classes de utilitários são escritas - para que possam ser usadas repetidamente por nossos muitos aplicativos que todos iremos escrever no futuro. Direito?

Felicidades.

→ Outras histórias de Greg Perry

DECODE Flutter no YouTube