Уведомления и сигналы тревоги во Flutter

Nov 10 2020
Работа с плагином Flutter, Android_Alarm_Manager. Знаю это! Этот плагин работает только на платформе Android! Я лично не знаю эквивалента для iOS.

Работа с плагином Flutter, Android_Alarm_Manager.

Знаю это! Этот плагин работает только на платформе Android ! Я лично не знаю эквивалента для iOS. Кроме того, через месяц после публикации этой статьи я наткнулся на плагин, который предоставляет уведомления на платформах Android и iOS. Я, конечно, написал статью и эту тоже. Увидеть ниже.

Уведомления во Flutter

Знайте, что если вы хотите использовать описанный здесь плагин, вы должны явно следовать его файлу readme для правильной настройки. Ваш AndroidManfest.xml должен хотя бы выглядеть примерно так:

<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

В данном случае это для включения будильника в вашем приложении! В моем случае я обнаружил, что плагин Flutter, android_alarm_manager , отвечал требованиям недавнего приложения, над которым я работал, и поэтому ... Я сделал рутину, чтобы легко с ним работать. Если хотите, сделайте копию , сделайте ее своей и поделитесь своими улучшениями. Круто?

Теперь, если у вас есть время, продолжайте читать, что я обнаружил, что мне пришлось сделать, чтобы этот плагин работал почти «безупречно», чтобы легко и быстро установить будильник или любую другую операцию, которая должна выполняться «в фоновом режиме» в какой-то момент. вовремя в будущем, пока приложение работает. Это делает жизнь немного легче, и это хорошо. Правильно?

Только скриншоты. Щелкните для Gists.

Как всегда, я предпочитаю использовать скриншоты, а не суть, чтобы показать код в своих статьях. Я считаю, что с ними легче работать и легче читать. Однако вы можете щелкнуть / коснуться их, чтобы увидеть код в сущности или в Github. По иронии судьбы, эту статью о мобильной разработке лучше читать на компьютере, чем на телефоне. Кроме того, мы программируем в основном на наших компьютерах; не на наших телефонах. На данный момент.

Давайте начнем.

Другие рассказы Грега Перри

Мой подход здесь состоит в том, чтобы сначала представить этот служебный класс в примере и продемонстрировать, как его использовать, используя таким образом плагин Flutter, android_alarm_manager . Фактически, я буду использовать тот же пример, что и на странице с примерами плагина . Однако этот пример был изменен для использования вместо него файла библиотеки, представленного здесь. Копия этого примера доступна вам как суть, android_alarm_manager . После примера я пройдусь по частям самого служебного класса, время от времени описывая, что необходимо сделать, чтобы позволить использовать такой класс непоколебимой массой, которая является широкой публикой.

Сохраняйте статичность

В этом очень простом примере вы должны нажать отображаемую кнопку, и через 5 секунд эти два нуля, как показано на скриншоте ниже, превратятся в единицы. Уууу! Что действительно интересно, конечно, так это то, что находится под капотом кода.

Обратите внимание: я изменил код с оригинала, чтобы учесть асинхронные операции, которые необходимо выполнить до того, как приложение действительно сможет начать работу. Для этого я использовал виджет FutureBuilder. При этом я определил новую функцию initSettings () для инициализации процедуры «Общие настройки», используемой для «запоминания» общего количества сработавших сигналов тревоги, как показано на скриншоте выше.

android_alarm_manager.dart

Вы также можете увидеть в функции initSettings (), показанной ниже, она устанавливает «общий счет» равным нулю, если приложение запускается впервые. Однако в самом начале библиотечная процедура AlarmManager инициализируется для настройки особой «Службы тревог» , необходимой для отправки уведомлений на телефоне Android.

android_alarm_manager.dart

Давайте рассмотрим пример кода и посмотрим, что составляет эту маленькую кнопку. Обратите внимание, что подпрограмма библиотеки использует те же имена для параметров и функций, которые составляют подчеркивающий плагин Flutter, Android_Alarm_Manager . Лучше согласиться с такими вещами. Кроме того, как и собственная функция плагина oneShot () , версия этой библиотеки после вызова будет «ждать» в течение указанного времени, прежде чем запускать указанную процедуру обратного вызова. В случае этого измененного примера процедура обратного вызова представляет собой анонимную функцию, которая запускается по истечении 5 секунд. На скриншоте ниже приложение было фактически запущено снова, кнопка была нажата еще раз, указывая, благодаря общим настройкам, что кнопка была нажата дважды с момента ее первого запуска. Уии.

android_alarm_manager.dart

Присмотревшись к этой анонимной функции ниже, мы увидим, что ей передается единственный параметр типа integer. Вы можете догадаться, что то же самое значение id, переданное второму параметру в виде случайного числа с помощью функций Dart, Random (). NextInt (pow (2,31)) . Зачем теперь передавать это значение, если оно уже передано в параметр рядом с ним? Мы к этому еще вернемся.

На данный момент вы можете увидеть ниже, что функция обратного вызова, в свою очередь, вызывает функцию _incrementCounter (). Здесь текущее «общее количество» нажатий кнопок извлекается из процедуры «Общие настройки». Затем он обновляется на один, чтобы учесть текущее нажатие кнопки, и сохраняется обратно в Общие настройки. Затем экран приложения обновляется увеличивающейся переменной _counter с помощью функции setState (). Вы до сих пор за всем этим следили?

android_alarm_manager.dart

Сохранение статичности

В отличие от исходного примера (см. Ниже), в этом примере разрешено использовать анонимную функцию и не требуется специально использовать статическую функцию. Конечно, вам также разрешено использовать статическую функцию в этом примере - при условии, что она все еще принимает это единственное целочисленное значение. Однако в исходном примере это должна быть статическая функция или функция высокого уровня, определенная вне любого класса в этом библиотечном файле Dart - любой из них, как вы видите, является требованием при работе непосредственно с плагином Flutter, Android_Alarm_Manager .

Однако, если вы читали мои статьи, вы знаете, что мне нравятся варианты. Все дело в том, чтобы у меня были варианты. Я написал этот служебный класс, чтобы он был более удобным - например, чтобы разрешить анонимные функции. Таким образом, при таком расположении передача этого целого числа id в качестве параметра действительно позволяет функции быть статической функцией, функцией высокого уровня или анонимной функцией. Параметры! Опять же, скриншоты исходного примера следуют ниже:

Обратимся к самому служебному классу AlarmManager . Опять же, он предназначен для работы с плагином. И снова, многие параметры являются теми же самыми параметрами, которые используются плагином, и поэтому передаются в этот плагин, но не до некоторой обширной проверки параметров на допустимые значения. Еще одна необходимая черта таких служебных классов. Он делает всю работу, поэтому вам не нужно. Правильно?

На скриншоте ниже показана первая часть этого служебного класса. В статической функции init () мы видим, что плагин действительно инициализирован. Обратите внимание: любые досадные ошибки, которые могут возникнуть при попытке инициализации, будут обнаружены в инструкции try-catch . Это также необходимо для служебных классов. Затем в функции init () есть вспомогательный класс _Callback , который вызывается для инициализации необходимых средств для взаимодействия приложения с отдельным изолятом, используемым службой сигнализации в фоновом режиме.

Наконец, ниже вы можете видеть, что любые и все значения параметров, не равные нулю, назначаются конкретным, а также статическим свойствам, которые составляют служебный класс AlarmManager . Здесь не так много статики.

alarm_manager.dart

Вы обнаружите, что многие свойства и функции, составляющие этот класс, являются статическими. Однако выбор использовать статические члены в классе должен быть умеренным. Например, поскольку функция init () является статической, это означает, что ее можно вызывать где угодно, когда угодно и любое количество раз. Этот факт требует дополнительного рассмотрения. В этом случае самый первый однострочный оператор на скриншоте выше - это оператор if: 'i f (_init) return _init;'. Это было необходимо при написании этой функции init (). Теперь вы можете вызывать эту функцию столько раз, сколько захотите. Тем не менее, необходимые службы и плагины инициализируются только при первом вызове. Итак, в группе разработчиков, например, если вызов функции init () по ошибке сделан более одного раза, вреда не будет. Еще одна желательная характеристика полезного класса. Видишь, что я здесь делаю? Делает это своего рода «надежным». Правильно?

Начните свои настройки

Кстати, если эти параметры передаются этим статическим переменным, это означает, что в нашем примере у вас больше возможностей. Когда вызывается функция init (), настройки могли быть указаны здесь и сейчас. Это позволило бы любые и все последующие вызовы функций, ONESHOT (), oneShotAt () и периодические (), чтобы использовать эти настройки , если явно не обеспечивая их собственные. Я продемонстрировал это ниже. Вы можете увидеть различия, сделанные в примере кода, если бы вместо этого были использованы дополнительные параметры в функции init (). Остается только вызов функции oneShot с ее продолжительностью, идентификатором и необходимой функцией обратного вызова. Делает код немного чище. Параметры!

D Не помещайте init () в функцию build () ! Похоже, собственная функция init () плагина Flutter называется AndroidAlarmManager. initialize ();, может вызвать побочные эффекты или проблемы. В некоторых случаях он инициирует перестройку (подобно вызову функции setState ()). Вот почему в моем служебном классе есть отдельная функция init (). Желательно, чтобы его вызывали в начале вашего приложения - например, в виджете FutureBuilder с MaterialApp. Убедитесь сами, и в своем модифицированном примере попробуйте закомментировать функцию AlarmManger.init () из initSettings () и вместо этого поместите ее прямо перед ее функцией oneShot () (см. Ниже). После этого в вашем примере начнут появляться ошибки.

android_alarm_manager.dart

Возьми свой oneShot

хорошо, вернемся к служебному классу. В функции oneShot () первые три «обязательных» значения параметра проверяются на правильность и передаются в собственную функцию oneShot () плагина . Все, кроме параметра «Функция», обратного вызова . Вместо этого он добавляется к статическому объекту Map, однозначно идентифицируемому предоставленным целочисленным идентификатором, с помощью следующей команды _Callback.oneShots [id] = callback . Мы скоро к этому вернемся. Наконец, вы заметили, что есть вызов статической функции oneShot (), которая также находится в этом вспомогательном классе _Callback . Это необходимая «статическая функция», которая будет использоваться плагином. Остальные значения параметров остаются в том числе статических переменных, использующих оператор объединения с нулем, ?? . Оператор используется, поэтому, когда явный параметр не передается, вместо него используются значения в этих статических переменных. Возьми? Между прочим, все эти статические переменные инициализируются значениями по умолчанию, поэтому в сам плагин в конечном итоге не передаются нулевые значения. Ницца.

alarm_manager.dart

Не рискуй

Обратите внимание, что вызов самого плагина также заключен в оператор try-catch . Это потому, что это сторонняя программа. Мы не знаем, что может случиться, и, поскольку это служебный класс, мы не хотим приводить к сбою вашего приложения, а вместо этого перехватываем любые исключения, которые могут возникнуть.

Кроме того, как и любой хороший служебный класс, у этого класса есть средства для разработчика «проверить», была ли операция успешной или нет. Если нет, любое исключение, которое могло произойти, записывается, чтобы разработчик мог его обработать. Это показано ниже в модифицированном примере с недавно вставленным оператором if .

android_alarm_manager.dart

Более статичный

Далее в служебном классе AlarmManager . Мы видим функцию oneShotAt (). Опять же, поскольку все эти функции являются статическими, в код необходимо включить некоторые меры безопасности. При неудачных обстоятельствах, например, плагин может не инициализироваться сначала при вызове этой функции onShotAt (). Другими словами, эта функция init () не вызывалась первой. Это могло произойти при использовании широкой публикой. На скриншоте ниже видно, что такая ситуация проверяется с помощью функции assert (). Это в надежде, что разработчик поймает такую ​​ошибку в процессе разработки. В производстве его ловит тот оператор if, который следует за функцией assert ().

Заметим, что это oneShotAt функция () имеет свой собственный объект на карте , чтобы сохранить переданный в функцию обратного вызова «», и она имеет свою собственную функцию статической, _Callback.onShatAt (), который будет передан собственный плагина oneShotAt функции (). Все это, кстати, подразумевает, что вы можете вызывать эти функции любое количество раз в своем приложении, планируя любое количество операций, которые должны произойти в будущем. Конечно, каждому должно быть присвоено собственное уникальное значение id, помните. В противном случае любая уже запланированная операция будет перезаписана новой, если будет использовано то же значение id. В этом и смысл использования уникальных идентификаторов. Правильно?

Однако все это также подразумевает, что вы можете использовать один и тот же идентификатор, но между тремя разными функциями, oneShot (), oneShotAt () и периодическим () по отдельности. Помните, что у них есть свои собственные отдельные объекты карты и статические функции. Этот факт хорошо послужил мне в моем недавнем проекте, где в качестве идентификатора использовались те самые значения, которые были найдены в основных полях резидентной базы данных. Варианты, детка! Любить это!

alarm_manager.dart

Обзор плагина

Теперь бегло взгляните на собственные функции oneShot () и oneShotAt () плагина Flutter , и вы увидите, что его функция oneShot () на самом деле просто передает свой параметр своему аналогу oneShotAt (). Обратите внимание, что объект CallbackHandle, который вы видите на снимке экрана ниже, исходит из функции _getCallbackHandle (), которая, в свою очередь, вызвала функцию фреймворка Flutter PluginUtilities.getCallbackHandle (обратный вызов) . Эта операция «отрывает» копию функции обратного вызова, чтобы иметь к ней доступ и вызывать такую ​​функцию в изолированном фоновом режиме. Я еще вернусь к этому.

Операция обратного вызова

Давайте пока продолжим и посмотрим на «вспомогательный класс» _Callback в файле библиотеки . Ниже вы можете увидеть, что те объекты карты, которые добавляются в функцию обратного вызова, определены в этом классе как статические свойства. В этом классе также есть функция init (), которая вызывается в собственной функции init () AlarmManger . Именно в этой функции init () регистрируется «порт связи» с тремя конкретными идентификаторами имени. Порт используется фоновой изоляцией для связи с изоляцией переднего плана, передавая ему сообщения. Угадайте, каковы значения этих идентификаторов имен? Они появляются на скриншоте ниже , хранящегося в переменных, _oneShot , _oneShotAt , и _periodic .

alarm_manager.dart

Как следует из названия, изоляты - это отдельные сегменты памяти
, которые по замыслу ... изолированы. Нет разделения памяти. Между изолятами происходит только передача сообщений. Содержимое такого сообщения может быть примитивным значением (null, num, bool, double, String), экземпляром объекта SendPort, объектом List или объектом Map с любым из этих примитивных значений, упомянутых первым.

Слушайте в фоновом режиме

Порт дополнительно назначается «слушателем», который будет реагировать, если и когда будет получено сообщение, в данном случае фоновой изоляцией, запускающей службу сигнализации. Слушатель представляет собой анонимную функцию, принимающую в качестве параметра объект Map. Вы можете видеть ниже, что объект Map содержит целочисленное значение (которое является идентификатором) и строку, в которой хранится один из этих «идентификаторов имени». Тогда оператор case определяет, какой «тип» функции должен запускаться. Видишь, как это работает? Конечно, поскольку это служебный класс, все это заключено в оператор try-catch . Например, мы понятия не имеем, что произойдет при запуске выбранной функции. Мы хотим перехватывать любые исключения, которые могут возникнуть. Правильно?

alarm_manager.dart

Отправить сообщение

Итак, как это сообщение отправляется в приложение, работающее на переднем плане? Что ж, как только эти идентификаторы имен зарегистрированы выше, тогда (см. Ниже) любая из трех функций служебного класса, AlarmManager . oneShot (), AlarmManager . oneShotAt () , и AlarmManager . периодическая (), будет проходить три соответствующие функции статических, _Callback . onShot (), _Callback . onShotAt () и _Callback . period (), прямо в плагин Flutter. Это позволит фоновой Isolate передать сообщение обратно в приложение, работающее в Isolate переднего плана. Все три типа вызовов перечислены ниже.

Видите ли, это три соответствующие статические функции _Callback . onShot (), _Callback . onShotAt () и _Callback . периодические (), которые являются «мостом» между изоляцией фона и изоляцией переднего плана. Например, когда пришло время включить будильник, служба будильника вызовет одну из этих трех статических функций. Обратите внимание, что это не фактическая функция, определенная в изолированном переднем плане, а ее «оторванная копия». Из-за этого вы увидите некоторое противоречивое поведение. Например, любая статическая переменная в этой функции, обычно определяемая в изолированном переднем плане, будет иметь значение null в фоновом изолировании. Вы можете сами проверить это явление.

Например, в нашем модифицированном примере, если я нажал кнопку в третий раз, мы знаем, что в объекте Map, oneShots , есть один объект Function, который запускается и обновляет экран через пять секунд, и он делает именно это на снимке экрана. процедуры "Слушатель" ниже. Однако в процессе этот объект Map становится пустым, если к нему обращаются в фоновом режиме Isolate ?! Как такое возможно? Это возможно, потому что это копия объекта Map, а не тот, который находится на переднем плане Isolate. Опять же, Isolates могут передавать друг другу только «сообщения». Они не разделяют память.

Опять же, эти три статические функции находятся во вспомогательном классе _Callback и перечислены одна за другой. Они показаны на скриншоте ниже. В каждом из них вы можете видеть, что на Isolate переднего плана ссылаются с помощью «идентификаторов имен» и передается объект Map из фонового Isolate. Обратите внимание, что оператор условного доступа к члену ?. , используется в случае, если операция поиска возвращает значение null. Так будет, например, если имя не существует. Это вряд ли когда-либо произойдет, поскольку это весь внутренний код, но, будучи служебным классом, мы не рискуем. Правильно?

Все это в конце файла библиотеки, и это здесь , где мы , наконец , увидеть значение этих «идентификаторов имен» в переменных, _oneShot , _oneShotAt , и _periodic . Каждый из них назван в честь соответствующего типа функции. Не очень образно, но имеет смысл. Мы также видим, что это переменные высокого уровня, определенные вне класса или функции высокого уровня. Фактически, это постоянные переменные с ключевым словом const . Как и переменные final, переменные const инициализируются только один раз и не могут быть изменены. В отличие от переменных final, они определяются при компиляции приложения. Следовательно, для наших нужд здесь они доступны даже для фоновых изоляторов. Ницца.

alarm_manager.dart

Если вы не разбираетесь во всей этой штуке с Isolate. Не беспокойся об этом прямо сейчас. Вот для чего предназначен служебный класс. В отличие от исходного кода примера плагина, вам не нужно беспокоиться о настройке «портов связи» или о том, когда использовать статическую или высокоуровневую функцию или переменную. Именно поэтому я написал этот класс в первую очередь - так что мне тоже не нужно беспокоиться обо всем этом. Вот почему такие служебные классы вообще пишутся - чтобы их можно было использовать снова и снова в наших многочисленных приложениях, которые мы все будем писать в будущем. Правильно?

Ура.

→ Другие рассказы Грега Перри

ДЕКОДИРОВАТЬ Flutter на YouTube