Powiadomienia i alarmy w Flutter
Praca z wtyczką Flutter, Android_Alarm_Manager.
Wiem to! Ta wtyczka działa tylko na platformę Android ! Osobiście nie znam żadnego odpowiednika iOS. Ewentualnie miesiąc po opublikowaniu tego artykułu natknąłem się na wtyczkę, która zapewnia powiadomienia na platformach Android i iOS. Oczywiście napisałem artykuł i to też. Zobacz poniżej.
Wiedz, że jeśli chcesz korzystać z opisanej tutaj wtyczki, musisz wyraźnie postępować zgodnie z jej plikiem readme , aby ustawić wszystko poprawnie. Twój AndroidManfest.xml powinien przynajmniej wyglądać jak poniżej:
<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
W tym przypadku służy do włączania alarmów w Twojej aplikacji! W moim przypadku znalazłem wtyczkę Flutter, android_alarm_manager , która spełniała wymagania ostatniej aplikacji, nad którą pracowałem, więc… Zrobiłem rutynę, aby łatwo z nią pracować. Jeśli chcesz, zrób kopię , zrób ją własną i podziel się wszelkimi wprowadzonymi ulepszeniami. Fajne?
Teraz, jeśli masz czas, kontynuuj czytanie, co stwierdziłem, że musiałem zrobić, aby ta wtyczka działała prawie `` niezawodnie '', aby łatwo i szybko ustawić alarm lub inną operację do wykonania w pewnym momencie `` w tle '' w przyszłości, gdy aplikacja jest uruchomiona. To sprawia, że życie jest trochę łatwiejsze i to dobrze. Dobrze?
Tylko zrzuty ekranu. Kliknij opcję Gists.
Jak zawsze wolę używać zrzutów ekranu zamiast streszczeń, aby pokazać kod w moich artykułach. Uważam, że łatwiej się z nimi pracuje i łatwiej je czytać. Możesz jednak kliknąć / dotknąć ich, aby zobaczyć kod w skrócie lub na Github. Jak na ironię, lepiej przeczytać ten artykuł o programowaniu mobilnym na komputerze niż na telefonie. Poza tym programujemy głównie na naszych komputerach; nie w naszych telefonach. Na razie.
Zaczynajmy.
Moje podejście polega na tym, aby najpierw przedstawić tę klasę narzędziową na przykładzie i zademonstrować, jak z niej korzystać - wykorzystując w ten sposób wtyczkę Flutter, android_alarm_manager . W rzeczywistości użyję tego samego przykładu wymienionego na własnej przykładowej stronie wtyczki . Jednak ten przykład został zmodyfikowany w celu wykorzystania przedstawionego tutaj pliku biblioteki. Kopia tego przykładu jest dostępna jako sedno, android_alarm_manager . Po tym przykładzie przejdę przez części samej klasy użytkowej, opisując czasami, co należało zrobić, aby umożliwić wykorzystanie takiej klasy przez nieugięte masy, czyli ogół społeczeństwa.
Zachowaj statyczność
W tym bardzo prostym przykładzie masz nacisnąć wyświetlony przycisk, a 5 sekund później te dwa zera, jak pokazano na poniższym zrzucie ekranu, zamieni się w jedynki. Uff! Naprawdę interesujące jest oczywiście to, co kryje się pod maską w kodzie.
Uwaga: zmieniłem kod z oryginału, aby uwzględnić operacje asynchroniczne, które należy wykonać, zanim aplikacja będzie mogła faktycznie zacząć działać. W tym celu wykorzystałem widżet FutureBuilder. Robiąc to, zdefiniowałem nową funkcję o nazwie initSettings () , aby zainicjować procedurę „Shared Preferences” używaną do „zapamiętywania” łącznej liczby uruchomionych alarmów, jak widać na powyższym zrzucie ekranu.
Możesz również zobaczyć w funkcji initSettings () wyświetlonej poniżej, która ustawia 'total count' na zero, jeśli jest to pierwsze uruchomienie aplikacji. Jednak na początku inicjowana jest procedura biblioteczna, AlarmManager , w celu skonfigurowania określonej „usługi alarmowej” niezbędnej do wykonywania powiadomień na telefonie z systemem Android.
Przejdźmy dalej w dół przykładowego kodu i zobaczmy, co składa się na ten mały przycisk. Należy zauważyć, że procedura biblioteczna używa tych samych nazw parametrów i funkcji, które składają się na podkreślającą wtyczkę Flutter, Android_Alarm_Manager . Lepiej być konsekwentnym w takich sprawach. Ponadto, podobnie jak własna funkcja oneShot () wtyczki, wersja tej biblioteki, po wywołaniu, „czeka” przez określony czas przed uruchomieniem określonej procedury wywołania zwrotnego. W przypadku tego zmodyfikowanego przykładu procedura wywołania zwrotnego jest funkcją anonimową, która zostanie uruchomiona po upływie 5 sekund. Na poniższym zrzucie ekranu aplikacja w rzeczywistości została ponownie uruchomiona, a przycisk został ponownie naciśnięty, co oznacza, że dzięki preferencjom współdzielonym przycisk został naciśnięty dwukrotnie od czasu jego pierwszego uruchomienia. Uff.
Przyjrzyjmy się bliżej tej anonimowej funkcji poniżej i widzimy, że przekazywany jest do niej pojedynczy parametr typu integer. Można się domyślić, że jest to ta sama wartość id przekazywana do drugiego parametru jako liczba losowa za pomocą funkcji Darta, Random (). NextInt (pow (2,31)) . Po co więc zawracać sobie głowę przekazywaniem tej wartości, skoro jest już przekazana do parametru znajdującego się tuż obok? Dojdziemy do tego.
Na razie możesz dalej zobaczyć poniżej, że funkcja zwrotna z kolei wywołuje funkcję _incrementCounter (). Tam bieżąca „całkowita liczba” naciśnięć przycisków jest pobierana z procedury „Preferencje wspólne”. Następnie jest aktualizowany o jeden w celu uwzględnienia bieżącego naciśnięcia przycisku i zapisywany z powrotem we wspólnych preferencjach. Ekran aplikacji jest następnie aktualizowany za pomocą rosnącej zmiennej _counter , przy użyciu funkcji setState (). Czy śledziłeś to wszystko do tej pory?
Utrzymywanie statyczności
W przeciwieństwie do oryginalnego przykładu (patrz poniżej), ten przykład może używać funkcji anonimowej i nie jest wymagane, aby używać funkcji statycznej. Oczywiście w tym przykładzie możesz również użyć funkcji statycznej - o ile nadal akceptuje tę samotną wartość całkowitą. Jednak w oryginalnym przykładzie musi to być funkcja statyczna lub funkcja wysokiego poziomu zdefiniowana poza jakąkolwiek klasą w tym pliku Dart biblioteki - jak widać, każda z nich jest wymagana podczas pracy bezpośrednio z wtyczką Flutter, Android_Alarm_Manager .
Jeśli jednak śledziłeś moje artykuły, wiesz, że lubię opcje. Chodzi o to, aby mieć ze mną opcje. Napisałem tę klasę narzędziową, aby była bardziej przyjazna - na przykład aby umożliwić anonimowe funkcje. Dlatego przy takim układzie przekazanie tej liczby całkowitej id jako parametru w rzeczywistości pozwala tej funkcji być funkcją statyczną, funkcją wysokiego poziomu lub funkcją anonimową. Opcje! Ponownie zrzuty ekranu oryginalnego przykładu znajdują się poniżej:
Przejdźmy do samej klasy narzędziowej AlarmManager . Ponownie, jest zaprojektowany do pracy z wtyczką. I znowu, wiele parametrów jest dokładnie tym samym parametrem używanym przez wtyczkę, a więc jest przekazywanych do tej wtyczki - ale nie przed obszernym testowaniem parametrów pod kątem prawidłowych wartości. Kolejna niezbędna cecha takich klas użytkowych. Wykonuje całą pracę, więc nie musisz. Dobrze?
Na poniższym zrzucie ekranu jest pierwsza część tej klasy użytkowej. Widzimy, że w funkcji statycznej init () wtyczka jest rzeczywiście zainicjowana. Uwaga: wszelkie niefortunne błędy, które mogą wystąpić podczas próby inicjalizacji, zostaną przechwycone w instrukcji try-catch . Klasy użytkowe też muszą to robić. Następnie w funkcji init () znajduje się klasa „pomocnicza” _Callback , która jest wywoływana w celu zainicjowania środków niezbędnych do komunikowania się aplikacji z oddzielną izolacją używaną przez usługę alarmową w tle.
Na koniec widać poniżej, że wszystkie wartości parametrów inne niż null są przypisane do określonych, a także statycznych właściwości, które składają się na klasę narzędziową AlarmManager . Dużo Static tu się nie dzieje.
Przekonasz się, że wiele właściwości i funkcji składających się na tę klasę jest statycznych. Jednak wybór użycia statycznych elementów członkowskich w klasie powinien być umiarkowany. Na przykład, ponieważ funkcja init () jest statyczna, oznacza to, że można ją wywołać w dowolnym miejscu, w dowolnym czasie i dowolną liczbę razy. Fakt ten wymaga dodatkowego rozważenia. W tym przypadku pierwszą jednowierszową instrukcją na powyższym zrzucie ekranu jest instrukcja if: 'i f (_init) return _init;'. Było to konieczne podczas pisania tej funkcji init (). Dzięki temu możesz teraz wywoływać tę funkcję tyle razy, ile chcesz. Niezależnie od tego niezbędne usługi i wtyczki są inicjalizowane dopiero przy pierwszym wywołaniu. I tak, na przykład w zespole programistów, jeśli wywołanie funkcji init () zostanie omyłkowo wykonane więcej niż raz, nic się nie stanie. Kolejna pożądana cecha klasy użytkowej. Widzisz co ja tu robię? Dzięki temu jest trochę „niezawodny”. Dobrze?
Zainicjuj swoje ustawienia
Nawiasem mówiąc, ponieważ te parametry są przekazywane do tych zmiennych statycznych, oznacza to, że w naszym przykładzie masz więcej opcji. Gdy wywoływana jest funkcja init (), ustawienia mogły zostać określone od czasu do czasu. Pozwoliłoby to wszystkim kolejnym wywołaniom funkcji oneShot (), oneShotAt () i periodic () na użycie tych ustawień, o ile nie zostaną one jawnie podane . Pokazałem to poniżej. Możesz zobaczyć różnice wprowadzone w przykładowym kodzie, jeśli zamiast tego zostały użyte dodatkowe parametry w funkcji init (). To po prostu pozostawia wywołanie funkcji „oneShot” z czasem trwania, identyfikatorem i niezbędną funkcją zwrotną. Sprawia, że kod jest trochę bardziej przejrzysty. Opcje!
D Nie umieszczaj init () w funkcji build () ! Wydawałoby się, że jest to własna funkcja init () wtyczki Flutter o nazwie AndroidAlarmManager. initialize ();, może powodować skutki uboczne lub problemy. W niektórych przypadkach zainicjuje przebudowę (podobnie jak wywołanie funkcji setState ()). Dlatego moja klasa narzędziowa ma oddzielną funkcję init (). Najlepiej, aby była wywoływana blisko początku aplikacji - na przykład w widżecie FutureBuilder z MaterialApp. Przekonaj się sam i w zmodyfikowanym przykładzie spróbuj skomentować funkcję AlarmManger.init () z initSettings () i umieść ją tuż przed funkcją oneShot () (patrz poniżej). Twój przykład zacznie wtedy napotykać błędy.
Take Your OneShot
ok, wracając do klasy użytkowej. W funkcji oneShot () pierwsze trzy „wymagane” wartości parametrów sprawdzają poprawność i są przekazywane do własnej funkcji oneShot () wtyczki . Wszystko oprócz parametru „Funkcja”, wywołanie zwrotne . Zamiast tego jest dodawany do statycznego obiektu Map jednoznacznie identyfikowanego przez podany identyfikator całkowity przy użyciu następującego polecenia _Callback.oneShots [id] = callback . Wkrótce do tego wrócimy. Na koniec zauważasz, że istnieje wywołanie funkcji statycznej oneShot (), również znajdującej się w tej klasie pomocniczej _Callback . Stanowi niezbędną „funkcję statyczną”, która ma być używana przez wtyczkę. Pozostawia to pozostałe wartości parametrów do przyjęcia tych wielu zmiennych statycznych przy użyciu operatora łączącego wartość null, ?? . Operator jest używany, więc gdy nie jest przekazywany jawny parametr, zamiast niego używane są wartości w tych zmiennych statycznych. Zdobyć? Nawiasem mówiąc, wszystkie te zmienne statyczne są inicjowane z wartościami domyślnymi, więc żadne wartości null nie są ostatecznie przekazywane do samej wtyczki. Ładny.
Nie ryzykuj
Zwróć uwagę, że wywołanie samej wtyczki jest również zawarte w instrukcji try-catch . To dlatego, że jest to program innej firmy. Nie wiemy, co może się zdarzyć, a ponieważ jest to klasa narzędziowa, nie chcemy zawieszać aplikacji, ale zamiast tego łapać wyjątki, które mogą wystąpić.
Ponadto, jak każda dobra klasa użytkowa, ta klasa ma środki dla programisty „do sprawdzenia”, czy operacja się powiodła, czy nie. Jeśli nie, każdy wyjątek, który mógł wystąpić, jest rejestrowany, aby programista mógł go następnie obsłużyć. Jest to pokazane poniżej w zmodyfikowanym przykładzie z nowo wstawioną instrukcją if .
Bardziej statyczny
Dalej w klasie narzędziowej AlarmManager . Widzimy funkcję oneShotAt (). Ponownie, ponieważ wszystkie te funkcje są funkcjami statycznymi, niektóre zabezpieczenia muszą zostać włączone do kodu. Na przykład w niefortunnych okolicznościach wtyczka może nie zostać zainicjowana najpierw po wywołaniu tej funkcji onShotAt (). Innymi słowy, funkcja init () nie została wywołana jako pierwsza. Może się to zdarzyć, gdy jest używany przez ogół społeczeństwa. Na poniższym zrzucie ekranu widać, że taka sytuacja jest testowana za pomocą funkcji assert (). Jest to nadzieja, że programista złapie taki błąd podczas opracowywania. W produkcji łapie się na tym, że instrukcja if , która następuje po funkcji assert ().
Uwaga, to oneShotAt function () ma swój własny obiekt na mapie, aby zapisać przekazywane w funkcji „zwrotnego”, i ma swoją własną funkcję statycznego, _Callback.onShatAt (), które mają być przekazane do wtyczki własnej oneShotAt function (). To wszystko oznacza, przy okazji, możesz wywołać te funkcje dowolną liczbę razy w aplikacji, planując dowolną liczbę operacji, które mają wystąpić w przyszłości. Oczywiście, każdy musi mieć przypisaną własną, niepowtarzalną wartość identyfikatora. W przeciwnym razie jakakolwiek już zaplanowana operacja zostanie nadpisana nową, jeśli będzie używać tej samej wartości identyfikatora. O to właśnie chodzi, gdy używasz unikalnych identyfikatorów. Dobrze?
Jednak wszystko to oznacza również, że możesz użyć tego samego identyfikatora, ale osobno między trzema różnymi funkcjami, oneShot (), oneShotAt () i periodic (). Pamiętaj, że mają własne oddzielne obiekty Map i funkcje statyczne. Fakt ten dobrze mi się przydał w moim ostatnim projekcie, w którym używane były same wartości znalezione w podstawowych polach rezydentnej bazy danych. Opcje, kochanie! Kocham to!
Podgląd wtyczki
Rzućmy teraz okiem na własne funkcje oneShot () i oneShotAt () wtyczki Flutter i zobaczysz, że funkcja oneShot () w rzeczywistości po prostu przekazuje swój parametr do swojego odpowiednika oneShotAt (). Zauważ, że obiekt 'CallbackHandle', który widzisz na poniższym zrzucie ekranu, pochodzi z funkcji _getCallbackHandle (), która z kolei nazwała funkcję szkieletową Fluttera , PluginUtilities.getCallbackHandle (callback) . Ta operacja „zrywa” kopię funkcji zwrotnej, aby mieć do niej dostęp i wywołać taką funkcję w Isolate działającej w tle. Do tego też wrócę.
Operacja oddzwaniania
Na razie kontynuujmy i spójrzmy na „klasę pomocniczą” _Callback w pliku biblioteki . Poniżej widać, że obiekty Map, które są dodawane w obiektach funkcji Callback, są zdefiniowane w tej klasie jako właściwości statyczne. Ta klasa ma również funkcję init () i jest wywoływana we własnej funkcji init () programu AlarmManger . Jest w tej funkcji init (), w której 'port komunikacji' jest zarejestrowany z trzema określonymi identyfikatorami nazw. Port jest używany przez izolację tła do komunikacji z izolacją pierwszego planu poprzez przekazywanie do niej komunikatów. Zgadnij, jakie są wartości tych identyfikatorów nazw? Pojawiają się one na zrzucie poniżej przechowywane w zmiennych, _oneShot , _oneShotAt , a _periodic .
Jak sama nazwa wskazuje, izolaty to oddzielne segmenty pamięci
, które zgodnie z projektem są… izolowane. Nie ma współdzielenia pamięci. Jest tylko przekazywanie wiadomości między izolatami. Treść takiej wiadomości może być wartością pierwotną (null, num, bool, double, String), instancją obiektu SendPort, obiektem List lub obiektem Map z dowolną z wymienionych wartości pierwotnych.
Słuchaj w tle
Portowi jest ponadto przypisany „nasłuchiwacz”, który reaguje, jeśli i kiedy nadejdzie wiadomość, w tym przypadku, przez izolację działającą w tle. Odbiornik ma postać anonimowej funkcji przyjmującej jako parametr obiekt Map. Możesz zobaczyć poniżej, że obiekt Map zawiera wartość całkowitą (która jest identyfikatorem) i String przechowujący jeden z tych „identyfikatorów nazw”. Instrukcja case określa następnie, który „typ” funkcji ma zostać uruchomiony. Widzisz, jak to działa? Oczywiście, będąc klasą użytkową, wszystko jest zawarte w instrukcji try-catch . Na przykład nie mamy pojęcia, co się stanie po uruchomieniu wybranej funkcji. Chcemy wyłapać wszelkie wyjątki, które mogą wystąpić. Dobrze?
Wysłać wiadomość
Jak więc ta wiadomość jest wysyłana do aplikacji działającej na pierwszym planie? Cóż, po zarejestrowaniu tych identyfikatorów nazw powyżej (patrz poniżej) dowolna z trzech funkcji klasy narzędzi, AlarmManager . oneShot (), AlarmManager . oneShotAt () , i AlarmManager . periodic (), przekaże trzy odpowiednie funkcje statyczne, _Callback . onShot (), _Callback . onShotAt () i _Callback . periodic (), bezpośrednio do wtyczki Flutter. Dzięki temu izolacja tła będzie mogła następnie przekazać komunikat z powrotem do aplikacji działającej na pierwszym planie Isolate. Wszystkie trzy rodzaje połączeń są wymienione poniżej.
Widzisz, to te trzy odpowiadające im funkcje statyczne, _Callback . onShot (), _Callback . onShotAt () i _Callback . periodic (), czyli „most” z tła Izoluj na pierwszy plan Izoluj. Na przykład, gdy nadejdzie pora na uruchomienie alarmu, usługa alarmowa wywoła jedną z tych trzech funkcji statycznych. Zauważ, że tak się składa, że nie jest to faktyczna funkcja zdefiniowana na pierwszym planie Isolate, ale jej „oderwana kopia”. Z tego powodu zobaczysz zachowanie sprzeczne z intuicją. Na przykład każda zmienna statyczna w tej funkcji, normalnie zdefiniowana na pierwszym planie Isolate, będzie miała wartość Null w tle Isolate. Możesz sam przetestować to zjawisko.
Na przykład w naszym zmodyfikowanym przykładzie, jeśli nacisnąłem przycisk po raz trzeci, wiemy, że obiekt Map, oneShots , zawiera ten jeden obiekt Function, aby uruchomić i zaktualizować ekran po pięciu sekundach, i robi to właśnie na zrzucie ekranu poniższej procedury „Listener”. Jednak w trakcie tego procesu obiekt mapy jest pusty, jeśli dostęp do niego odbywa się w tle. Izolować ?! Jak to możliwe? Jest to możliwe, ponieważ jest to kopia obiektu Map, a nie ta na pierwszym planie Isolate. I znowu, izolatorzy mogą przekazywać sobie tylko „wiadomości”. Nie dzielą pamięci.
Ponownie, te trzy funkcje statyczne znajdują się w klasie pomocniczej _Callback i są wymienione jedna po drugiej. Są wyświetlane na poniższym zrzucie ekranu. W każdym z nich można zobaczyć, że pierwszy plan Isolate jest powiązany z 'identyfikatorami nazw' i jest przekazywany obiekt Map z izolowanego tła. Uwaga, operator warunkowego dostępu do elementu członkowskiego, ?. , jest używany w przypadku, gdy operacja wyszukiwania zwraca wartość null. Zrobi to, jeśli na przykład nazwa nie istnieje. To prawdopodobnie nigdy się nie wydarzy, ponieważ jest to cały kod zinternalizowany, ale będąc klasą użytkową, nie ryzykujemy. Dobrze?
Wszystko to jest na końcu pliku biblioteki, i to tutaj, gdzie w końcu zobaczyć wartość tych „Identyfikatory” w zmiennych, _oneShot , _oneShotAt , a _periodic . Każdy z nich nosi nazwę odpowiadającą typowi funkcji. Niezbyt pomysłowe, ale ma sens. Widzimy również, że są to zmienne wysokiego poziomu zdefiniowane poza klasą lub funkcją wysokiego poziomu. W rzeczywistości są to zmienne stałe ze słowem kluczowym const . Podobnie jak zmienne końcowe, zmienne const są inicjalizowane tylko raz i nie można ich później zmienić. W przeciwieństwie do zmiennych końcowych są one definiowane podczas kompilacji aplikacji. Dlatego dla naszych potrzeb są one dostępne nawet dla izolatów tła. Ładny.
Jeśli nie masz pojęcia o tym wszystkim, izoluj. Nie martw się tym teraz. Do tego służy klasa użyteczności. W przeciwieństwie do oryginalnego przykładowego kodu wtyczki, nie musisz się martwić o konfigurację „portów komunikacyjnych” ani o to, kiedy użyć funkcji statycznej lub funkcji lub zmiennej wysokiego poziomu. Dlatego właśnie napisałem te zajęcia - więc też nie muszę się tym martwić. Właśnie dlatego takie klasy narzędziowe są w ogóle pisane - aby mogły być używane wielokrotnie w wielu naszych aplikacjach, które wszyscy będziemy pisać w przyszłości. Dobrze?
Twoje zdrowie.
→ Inne opowiadania Grega Perry'ego