Im struggling with this topic a few weeks. I registered on firebase console, pusher beams, everything setup as is written in documentation, I copied example project from flutter pusher_beams package so that when i send notification and app is opened, alert pop up and when is minimised, notification is shown in notification tray.
Problems:
When I tap on notification when app is minimised, i cannot check, which notification was tapped. I tried to add delay before that function but without help.
PusherBeams.instance.getInitialMessage is returning null, when i break there, but debugger dont stop on that breakpoint when i tap on notification when app is minimised.
I dont know how to handle notifications when app is killed. I found flutter_background_service, but when i set isForegroundMode to true, unhideable (if that word exists) notification is in notification tray and when i set isForegroundMode to false, when app is killed, that service is killed too. Another possibility is to use workmanager, but that can be called the fastest every 15 mins.
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
super.initState();
initPusherBeams();
}
initPusherBeams() async {
// Let's see our current interests
print(await PusherBeams.instance.getDeviceInterests());
// This is not intented to use in web
if (!kIsWeb) {
await PusherBeams.instance
.onInterestChanges((interests) => {
print('Interests: $interests')});
await PusherBeams.instance
.onMessageReceivedInTheForeground(_onMessageReceivedInTheForeground);
}
await _checkForInitialMessage();
}
Future<void> _checkForInitialMessage() async {
await Future.delayed(const Duration(seconds: 1));
final initialMessage = await PusherBeams.instance.getInitialMessage();
if (initialMessage != null) {
_showAlert('Initial Message Is:', initialMessage.toString());
}
}
void _onMessageReceivedInTheForeground(Map<Object?, Object?> data) {
_showAlert(data["title"].toString(), data["body"].toString());
}
void _showAlert(String title, String message) {
AlertDialog alert = AlertDialog(
title: Text(title), content: Text(message), actions: const []);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Related
I am creating an alarm clock application, and I want to show a full screen page to allow the user to dismiss the alarm when it triggers. Thats all working well but the issue arises when I want to close that page.
What I have tried
Currently, when the alarm triggers, I am pushing that page onto the navigation stack to make it visible:
App.navigatorKey.currentState?.pushNamedAndRemoveUntil(
alarmNotificationRoute,
(route) {
return (route.settings.name != '/alarm-notification') ||
route.isFirst;
},
);
And then pop it when user presses "Dismiss":
if (App.navigatorKey.currentState?.canPop() ?? false) {
App.navigatorKey.currentState?.pop();
}
My App routing code:
class App extends StatefulWidget {
const App({super.key});
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return MaterialApp(
...
navigatorKey: App.navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => const RootScreen());
case '/alarm-notification':
return MaterialPageRoute(
builder: (context) {
return AlarmNotificationScreen();
},
);
default:
assert(false, 'Page ${settings.name} not found');
return null;
}
},
);
}
}
Current behavior
Now when I pop, it returns to the default route of the flutter app '/', even when the alarm triggered while the app was closed.
Expected behavior
The behavior I want is as follows:
If the app was in the foreground when alarm triggered, pressing dismiss should go back to the last screen (this is already working as expected)
If the app was in the background or closed when alarm triggered, pressing dismiss should send the app to background
If android decides to show a Heads Up Notification instead of a full page intent. pressing dismiss should do nothing
Thoughts
I am thinking that the cleanest way to do so would be to launch a standalone page/activity, which we can just close when we press dismiss. Is there anyway to do such a thing? I am fine with it being an android-only solution.
There appears to be a minimize_app package that does the "close to background" behavior you want. From there it's simply a matter of tracking where the page was navigated from and using conditional logic.
A possible implementation:
import 'package:minimize_app/minimize_app.dart';
...
// Set this variable when the app is opened via the alarm trigger
if (appWasInBackground) {
MinimizeApp().minimizeApp();
} else if (App.navigatorKey.currentState?.canPop() ?? false) {
App.navigatorKey.currentState?.pop();
}
I have a todo app built in Flutter and intended only for Android. I built a home screen widget for it (using the home_widget package in Flutter) to allow users to see a list of tasks and check them off directly from the widget.
At midnight, the tasks should reset with the new tasks for the day (I used the workmanager package to accomplish this, although I also tried the android_alarm_manager_plus package, with the same results). All of this functionality is working perfectly in debug mode, and even in profile mode (I can't test it in release mode because, according to my understanding, that would remove services and thus the home_widget would not work; however, when I do the build, that doesn't seem to be the problem because the home widget still shows up). BUT! When I build the release APK and submit it to Google Play for internal testing, then download it onto my Pixel 7 (with no power saving modes on, as far as I'm aware), the midnight function does not run. :(
Here's the relevant code:
main_prod.dart
void main() async {
return mainGeneric('Prod Name', ProdFirebaseOptions.currentPlatform, Environment.prod);
}
main_generic.dart
/// Used for Background Updates using Workmanager Plugin
#pragma('vm:entry-point')
void workmanagerCallbackDispatcher() {
Workmanager().executeTask((taskName, inputData) {
if (taskName == 'widgetBackgroundUpdate') {
try {
return Future.wait<void>([
// This is a static Future<void> function from a helper class that resets
// the tasks; it seems to be working when I test it by itself, as well as
// in debug or profile mode.
MidnightService.resetTasks(),
]).then((value) {
return Future.value(true);
});
} catch(err) {
print(err.toString());
throw Exception(err);
}
}
return Future.value(true);
});
}
void _startBackgroundUpdate() async {
if (await MidnightService.shouldUpdateWorkManagerTasks()) {
(await SharedPreferences.getInstance()).setInt('midnight_tasks_update_version', Constants.MIDNIGHT_TASKS_UPDATE_VERSION);
await Workmanager().cancelAll();
DateTime now = DateTime.now();
int nowMillis = now.millisecondsSinceEpoch;
int midnightTonightMillis = DateTime(now?.year ?? 0, now?.month ?? 0, (now?.day ?? 0) + 1).millisecondsSinceEpoch;
int millisUntilMidnight = midnightTonightMillis - nowMillis;
await Workmanager().registerPeriodicTask('midnightUpdate', 'widgetBackgroundUpdate', initialDelay: Duration(milliseconds: millisUntilMidnight), frequency: Duration(days: 1));
}
}
void mainGeneric(String appName, FirebaseOptions firebaseOptions, Environment environment) async {
// Avoid errors caused by flutter upgrade.
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(workmanagerCallbackDispatcher, isInDebugMode: kDebugMode).then((_) => _startBackgroundUpdate());
...
// If this is the first time opening the app with widget functionality.
HomeWidget.getWidgetData<String>('todays_tasks_string', defaultValue: '').then((todaysTasksString) async {
if (todaysTasksString == '') {
List<Task> todaysTasks = await Repositories().taskRepository.getFocusedTasks();
await HomeWidgetUtils.setTodaysTasks(todaysTasks);
return true;
}
return false;
});
Firebase.initializeApp(
name: appName,
options: firebaseOptions,
).then((_) async {
...
});
HomeWidget.registerBackgroundCallback(homeWidgetBackgroundCallback);
runApp(AppConfig(
child: MyApp(),
environment: environment,
appTitle: appName,
));
}
// Called when doing background work initiated from home screen widget
#pragma('vm:entry-point')
Future<void> homeWidgetBackgroundCallback(Uri uri) async {
if (uri.host.startsWith('completetask_')) {
String todaysTasksString = await HomeWidgetUtils.updateTaskById(uri.host.split('_')[1], true);
await HomeWidget.saveWidgetData<String>('todays_tasks_string', todaysTasksString);
await HomeWidget.updateWidget(androidName: 'TodaysTasksWidgetProvider');
}
}
midnight_service.dart
class MidnightService {
...
static Future<bool> shouldUpdateWorkManagerTasks() async {
try {
final prefs = await SharedPreferences.getInstance();
int midnightTasksUpdateVersion = prefs.getInt('midnight_tasks_update_version');
return Constants.MIDNIGHT_TASKS_UPDATE_VERSION > midnightTasksUpdateVersion;
}
catch (e) { print(e); }
return true;
}
}
It might also be valuable to note that, when a user checks off a task from the home screen widget, sometimes the task takes a while to actually be checked off (and sometimes requires the app to be opened before it will execute). However, I figured this is just a slowness issue or something controlled by the OS that I can't do much about.
With all of that, my question is then, why is the workmanager not executing its midnight task?
I've been smashing my head against this for days, so any help and/or advice is greatly appreciated!!
There is a scheduled notification which is supposed to show after 5 seconds. This scheduled notification is being called inside the void initState function. I'm using this package to show notifications.
The notification is being shown exactly after 5 seconds, so there is no problem there.
The problem is that there is another function that is supposed to be called when the notification is clicked. But this function is being called long before the notification even appears and I don't know how is this happening. I have tried different approaches to solve this problem but non works.
Be low is the code where all this is happening.
class _HomePageState extends State<HomePage> {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
#override
void initState() {
super.initState();
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var initNotifSettings = new InitializationSettings(android, null);
flutterLocalNotificationsPlugin.initialize(initNotifSettings,
onSelectNotification: whenNotificationSelect);
showNotification();
}
Future whenNotificationSelect(String payload) async {
print('Payload: $payload');
Navigator.pushNamed(context, '/notifications');
}
showNotification() async {
var android = new AndroidNotificationDetails(
'channel id', 'channel name', 'channel description');
var platform = new NotificationDetails(android, null);
var scheduledNotificationDateTime =
DateTime.now().add(Duration(seconds: 2));
await flutterLocalNotificationsPlugin.schedule(
0,
'Good morning!',
'Greetings from me.',
scheduledNotificationDateTime,
platform,
payload: 'Simple App',
);
}
}
Note The function whenNotificationSelect it needs to be called when notification is being clicked, but for other reasons that I don't know this function is being called immediately when the app starts. I want whenNotificationSelect to be called only when the notification is clicked not when the app starts.
Thank you, so much Love.
Try this
onSelectNotification:(String payload) => whenNotificationSelect(String payload)
When you are not using (String payload) it means, the function will be triggered on null too. Whenever you have to pass arguments, use (arguments)=> functionName(arguments)
I'm making a LoginScreen, in LoginScreen i check data in database for know user logged or not for each times open app.
If user logged, the app will switched to HomeScreen.
I have a problem, i had logged in LoginScreen and then the app switched to HomeScreen. But my app's not standing in HomeScreen, it's continuing push new HomeScreen and looping this push action.
My code:
goToHomeIfAvailable() async {
// Go to HomeScreen if available
if (await this._databaseProvider.tokenTableIsEmpty() == false) {
print('Logged');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
}
}
#override
Widget build(BuildContext context) {
// In first times user open app=> create DB and go to HomeScreen if available
_databaseProvider.openOrCreate().then((_) async {
await goToHomeIfAvailable();
});
/* Return a widget bellow */
}
DatabaseProvider.dart:
class DatabaseProvider {
String _path = 'O2_DB.db';
Database _database;
Map _tableName = {'token': 'token_tbl'};
Future openOrCreate() async {
this._database = await openDatabase(this._path, version: 1,
onCreate: (Database db, version) async {
await db.execute('CREATE TABLE IF NOT EXISTS ' +
this._tableName['token'] +
' (token_id integer primary key autoincrement, token text)');
});
}
}
Build is called many times during the app life cycle - & its better to always put our logic outside build method. Its normal Behavior.
In your case - As build was called each time the method - goToHomeIfAvailable() was called hence multiple push.
Moving goToHomeIfAvailable() out of build to initState() will solve the issue.
I am trying to show a dialog in any active screen when a push notification arrives in my app. While the app is running. I can show the dialog by user interaction, like clicking a button. But I want to show it without user interaction. If there is a notification arrive, only then the dialog should be triggered. I am trying to call it with background fetch. But couldn't find any solution. So, please help and thank you in advance.
I faced same problem before, i will show my solution and I hope it suits you
The main thing with the solution : we have page is not pop from
Navigator stack when app is life like HomePage as ex, so we can use the BuildContext from this page
so by pass context of my StatefulWidget(Like Home Page) who
must be still in stack of Navigator (not pop it when app is live) to your class who handle notification data when coming you can use it to show dialog
Let write some code now:
as ex we have NotificationManger class this class used to handle notification msg with static method
class NotificationManger {
static BuildContext _context;
static init({#required BuildContext context}) {
_context = context;
}
//this method used when notification come and app is closed or in background and
// user click on it, i will left it empty for you
static handleDataMsg(Map<String, dynamic> data){
}
//this our method called when notification come and app is foreground
static handleNotificationMsg(Map<String, dynamic> message) {
debugPrint("from mangger $message");
final dynamic data = message['data'];
//as ex we have some data json for every notification to know how to handle that
//let say showDialog here so fire some action
if (data.containsKey('showDialog')) {
// Handle data message with dialog
_showDialog(data);
}
}
static _showDialog({#required Map<String, dynamic> data}) {
//you can use data map also to know what must show in MyDialog
showDialog(context: _context,builder: (_) =>MyDialog());
}
}
Now we have this callback as top level or static (must be one of them ) inside class FCM of my app inside it
class Fcm {
static final FirebaseMessaging _fcm = FirebaseMessaging();
static initConfigure() {
if (Platform.isIOS) _iosPermission();
_fcm.requestNotificationPermissions();
_fcm.autoInitEnabled();
_fcm.configure(
onMessage: (Map<String, dynamic> message) async =>
NotificationManger.handleNotificationMsg(message),
onLaunch: (Map<String, dynamic> message) async =>
NotificationManger.handleDataMsg(message['data']),
onResume: (Map<String, dynamic> message) async =>
NotificationManger.handleDataMsg(message['data']),
onBackgroundMessage: async =>
NotificationManger.handleDataMsg(message['data']);
}
static _iosPermission() {
_fcm.requestNotificationPermissions(
IosNotificationSettings(sound: true, badge: true, alert: true));
_fcm.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
}
}
For know more about callback fcm read this
ok now in our HomePage State well init our class inside initState method
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,(){
///init Notification Manger
NotificationManger.init(context: context);
///init FCM Configure
Fcm.initConfigure();
});
}
as say before the homePage will be not pop when app is show , you can start another page but without close the homePage
i hope this help