FlutterLocalNotification plugin with java error to execute - flutter

I'm trying to use the FlutterLocalNotification in my app and I'm getting errors when executing it. Can anyone know how can solve this? I try some similar questions but nothing works. (like included " proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'" inside the build.gradle)
class Notification extends StatefulWidget {
#override
_Notification createState() => _Notification();
}
class _Notification extends State<Notification> {
FlutterLocalNotificationsPlugin flutterNotification =
new FlutterLocalNotificationsPlugin();
#override
void initState() {
super.initState();
var androidInitilize = new AndroidInitializationSettings("app_icon");
var IOSinitialize = new IOSInitializationSettings();
var initializationSettings = new InitializationSettings(
android: androidInitilize, iOS: IOSinitialize);
flutterNotification = new FlutterLocalNotificationsPlugin();
flutterNotification.initialize(initializationSettings,
onSelectNotification: notificationSelected('play'));
}
Future _showNotification() async {
var androidDetails = new AndroidNotificationDetails(
"Channel ID", "Details",
importance: Importance.max);
var IOSDetails = new IOSNotificationDetails();
var generalNotificationDetails =
new NotificationDetails(android: androidDetails, iOS: IOSDetails);
await flutterNotification.show(
0,
"Title:",
"body",
generalNotificationDetails);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
notificationSelected(String payload) async {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text("Notification : $payload"),
),
);
}
}
class that calls the showNotification method:
class TriggeredNotification extends StatelessWidget {
final Map<dynamic, dynamic> _imageData;
ImagePage(this._imageData);
downloadImage() async {
try {
// Saved with this method.
var imageId = await ImageDownloader.downloadImage(
_imageData["images"]["fixed_height"]["url"],
destination: AndroidDestinationType.directoryDownloads);
if (imageId == null) {
print("imageid : $imageId");
return;
} else {
_Notification()._showNotification();
}
// Below is a method of obtaining saved image information.
var fileName = await ImageDownloader.findName(imageId);
var path = await ImageDownloader.findPath(imageId);
var size = await ImageDownloader.findByteSize(imageId);
var mimeType = await ImageDownloader.findMimeType(imageId);
print(
"filename: $fileName | path: $path | size: $size | mimetype: $mimeType");
} on PlatformException catch (error) {
print(error);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_imageData['title']),
backgroundColor: Colors.black,
actions: [
IconButton(
onPressed: () {
downloadImage();
},
icon: Icon(Icons.download))
],
),
backgroundColor: Colors.black,
body: Center(
child: Image.network(_imageData["images"]["fixed_height"]["url"]),
),
);
}
}
Error:
PlatformException (PlatformException(error, Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference, null, java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference
at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.setSmallIcon(FlutterLocalNotificationsPlugin.java:300)
at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.createNotification(FlutterLocalNotificationsPlugin.java:215)
at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.showNotification(FlutterLocalNotificationsPlugin.java:1024)
at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.show(FlutterLocalNotificationsPlugin.java:1362)
at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.onMethodCall(FlutterLocalNotificationsPlugin.java:1241)
at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:296)
at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:320)
at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
))

For statement : var androidInitilize = new AndroidInitializationSettings("app_icon");
it should be path to app_icon
see the following example, specifying launcher icon from mipmap folder of android app , you should write this
var androidInitilize = new AndroidInitializationSettings('#mipmap/ic_launcher')
or if its from drawable folder of android app then
var androidInitilize = new AndroidInitializationSettings('#drawable/app_icon')
let me know if its work you or not.

Related

Flutter - populating syncfusion calendar with data from Firebase

I am using the syncfusion_flutter_calendar package. My objective is to populate the calendar with data coming from Firestore.
When I try the code below, I am getting an error that I understand, but I do not find where to fix it. Please, can you help? Thank you.
Error : Unhandled Exception: type 'List' is not a subtype of type 'List'
var myQueryResult;
List<Color> _colorCollection = <Color>[];
MeetingDataSource? events;
final databaseReference = FirebaseFirestore.instance;
class CalendarLastTest extends StatefulWidget {
const CalendarLastTest({Key? key}) : super(key: key);
#override
State<CalendarLastTest> createState() => _CalendarLastTestState();
}
class _CalendarLastTestState extends State<CalendarLastTest> {
#override
void initState() {
_initializeEventColor();
getDataFromFireStore().then((results) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {});
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST AGENDA'),
),
body: SfCalendar(
view: CalendarView.month,
initialDisplayDate: DateTime.now(),
dataSource: events,
monthViewSettings: const MonthViewSettings(
appointmentDisplayMode: MonthAppointmentDisplayMode.indicator,
showAgenda: true),
),
);
}
Future<void> getDataFromFireStore() async {
var snapShotsValue = await myQuery();
final Random random = Random();
List<Meeting> list = snapShotsValue.docs
.map((e) => Meeting(
title: e.data()['name'],
description: e.data()['notes'],
from: DateFormat('yyyy-MM-dd HH:mm').parse(e.data()['start_Date']),
to: DateFormat('yyyy-MM-dd HH:mm').parse(e.data()['due_Date']),
backgroundColor: _colorCollection[random.nextInt(9)],
isAllDay: false))
.toList();
setState(() {
events = MeetingDataSource(list);
print (events);
});
}
Future myQuery () async {
// final provider = Provider.of<MeetingProvider>(context, listen: false);
//final provider = Provider.of<MeetingProvider> (context);
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
myQueryResult = currentQuery.where('done', isEqualTo : 'No');
myQueryResult =
myQueryResult.where('start_Date', isNotEqualTo: '');
// myQueryResult = myQueryResult.where('due_Date'.length, isEqualTo : 16);
final snapshot = await myQueryResult.get();
return snapshot;
}
void _initializeEventColor() {
_colorCollection = <Color>[];
_colorCollection.add(const Color(0xFF0F8644));
_colorCollection.add(const Color(0xFF8B1FA9));
_colorCollection.add(const Color(0xFFD20100));
_colorCollection.add(const Color(0xFFFC571D));
_colorCollection.add(const Color(0xFF36B37B));
_colorCollection.add(const Color(0xFF01A1EF));
_colorCollection.add(const Color(0xFF3D4FB5));
_colorCollection.add(const Color(0xFFE47C73));
_colorCollection.add(const Color(0xFF636363));
_colorCollection.add(const Color(0xFF0A8043));
}
}
The issue is that the children's type is ListMeeting> the map method did not return that information, resulting in the type exception. You must specify the type of argument (Meeting) to the map method in order to fix this error. Please see the code snippets below.
Future<void> getDataFromFireStore() async
{
var snapShotsValue = await myQuery();
final Random random = Random();
List<Meeting> list = snapShotsValue.docs
.map<Meeting>((e) => Meeting(
eventName: e.data()['name'],
// description: e.data()['notes'],
from: DateFormat('yyyy-MM-dd HH:mm').parse(e.data()['start_Date']),
to: DateFormat('yyyy-MM-dd HH:mm').parse(e.data()['due_Date']),
background: _colorCollection[random.nextInt(9)],
isAllDay: false))
.toList();
setState(() {
events = MeetingDataSource(list);
});
}
Future<void> getDataFromFireStore() async {
// get appointments
var snapShotsValue = await fireStoreReference
.collection("ToDoList")
.where('CalendarType', isNotEqualTo: 'personal')
.get();
// map meetings
List<Meeting> list = snapShotsValue.docs
.map((e) => Meeting(
eventName: e.data()['Subject'],
from: convertTimeStamp(e.data()['StartTime']), //write your own ()
to: convertTimeStamp(e.data()['EndTime']),
background: colorConvert(e.data()['color']), //write your own ()
isAllDay: e.data()['isAllDay'],
recurrenceRule: e.data()['RRULE'],
recurrenceId: e.id,
resourceIds: List.from(e.data()['resourceIds']),
notes: e.data()['notes'],
address: e.data()['Address'].toString(),
geolocation: e.data()['Location'],
calendarType: e.data()['CalendarType'],
id: e.reference,
key: e.id))
.toList();
//get staff then add all to MeetingDataSource
var snapShotsValue2 = await fireStoreReference
.collection("Users")
.where('isStaff', isEqualTo: true)
.get();
List<CalendarResource> resources = snapShotsValue2.docs
.map((e) => CalendarResource(
displayName: e.data()['display_name'],
id: e.reference,
image: NetworkImage(valueOrDefault<String>(
e.data()['photo_url'],
'https',
)),
))
.toList();
setState(() {
events = MeetingDataSource(list, resources);
_employeeCollection = resources;
});
}

Flutter download progress in notificaiton

Hi is there a way to show download progress in a notification in flutter apps, i know there are plugins that handle downloads and show download progress in notifications such as FlutterDownloader plugin.
I want to do this by myself using flutter, is there any way to do this or do i have to do it natively?.
Something Like this:
This is for anyone who does not want to use a package like FlutterDownloader to handle downloads but might want to use http or Dio and flutter_local_notifications to show progress when app is in the foreground.
First the provider class to handle state of in app circular progress indicator:
class DownloadProvider extends ChangeNotifier {
var _progressList = <double>[];
// double count = 0.0;
double currentProgress(int index) {
//fetch the current progress,
//its in a list because we might want to download
// multiple files at the same time,
// so this makes sure the correct download progress
// is updated.
try {
return _progressList[index];
} catch (e) {
_progressList.add(0.0);
return 0;
}
}
void download(String filePath, int index) async {
NotificationService notificationService = NotificationService();
final storageRef = FirebaseStorage.instance.ref().child(filePath);
final downloadUrl = await storageRef.getDownloadURL();
final fileName = storageRef.name;
final dio = Dio();
try {
dio.download(downloadUrl, "/storage/emulated/0/Download/$fileName",
onReceiveProgress: ((count, total) async {
await Future.delayed(const Duration(seconds: 1), () {
_progressList[index] = (count / total);
notificationService.createNotification(
100, ((count / total) * 100).toInt(), index);
notifyListeners();
});
}));
} on DioError catch (e) {
print("error downloading file $e");
}
}
}
class NotificationService {
//Hanle displaying of notifications.
static final NotificationService _notificationService =
NotificationService._internal();
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final AndroidInitializationSettings _androidInitializationSettings =
const AndroidInitializationSettings('ic_launcher');
factory NotificationService() {
return _notificationService;
}
NotificationService._internal() {
init();
}
void init() async {
final InitializationSettings initializationSettings =
InitializationSettings(
android: _androidInitializationSettings,
);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
void createNotification(int count, int i, int id) {
//show the notifications.
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'progress channel', 'progress channel',
channelDescription: 'progress channel description',
channelShowBadge: false,
importance: Importance.max,
priority: Priority.high,
onlyAlertOnce: true,
showProgress: true,
maxProgress: count,
progress: i);
var platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
_flutterLocalNotificationsPlugin.show(id, 'progress notification title',
'progress notification body', platformChannelSpecifics,
payload: 'item x');
}
}
Then in your main class for displaying the download you can connect to the provider, thus:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<DownloadProvider>(
builder: (context, value, ch) {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
value.download('your soul ft biswick', 0);
},
child: const Text("Download")),
CircularProgressIndicator(
value: value.currentProgress(0),
)
],
),
);
},
),
I hope this helps whoever in the future, i will figure out how to make this run in the background and edit my answer in the future. 🤞

Write a test for reading and writing files in dart

I am learning Flutter and Dart currently. Now I want to read and write files to memory. I have code for reading and writing. Now I want tests for that. Here is where I run into problems. I always get:
'package:flutter/src/services/platform_channel.dart': Failed assertion: line 134 pos 7: '_binaryMessenger != null || ServicesBinding.instance != null': Cannot use this MethodChannel before the binary messenger has been initialized. This happens when you invoke platform methods before the WidgetsFlutterBinding has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() before this or by passing a custom BinaryMessenger instance to MethodChannel().
dart:core _AssertionError._throwNew
package:flutter/src/services/platform_channel.dart 134:7 MethodChannel.binaryMessenger
package:flutter/src/services/platform_channel.dart 167:36 MethodChannel._invokeMethod
package:flutter/src/services/platform_channel.dart 350:12 MethodChannel.invokeMethod
package:path_provider_macos/path_provider_macos.dart 48:10 PathProviderMacOS.getApplicationDocumentsPath
package:path_provider/path_provider.dart 115:40 getApplicationDocumentsDirectory
package:skeet25pro/main_counter.dart 18:29 CounterStorage._localPath
package:skeet25pro/main_counter.dart 24:24 CounterStorage._localFile
package:skeet25pro/main_counter.dart 43:24 CounterStorage.writeCounter
test/file_io_test.dart 8:27 main.<fn>
test/file_io_test.dart 5:33 main.<fn>
main_counter.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(
MaterialApp(
title: 'Reading and Writing Files',
home: FlutterDemo(storage: CounterStorage()),
),
);
}
class CounterStorage {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/counter.txt');
}
Future<int> readCounter() async {
try {
final file = await _localFile;
// Read the file
final contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If encountering an error, return 0
return 0;
}
}
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
}
class FlutterDemo extends StatefulWidget {
const FlutterDemo({Key? key, required this.storage}) : super(key: key);
final CounterStorage storage;
#override
_FlutterDemoState createState() => _FlutterDemoState();
}
class _FlutterDemoState extends State<FlutterDemo> {
int _counter = 0;
#override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
setState(() {
_counter = value;
});
});
}
Future<File> _incrementCounter() {
setState(() {
_counter++;
});
// Write the variable as a string to the file.
return widget.storage.writeCounter(_counter);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Reading and Writing Files'),
),
body: Center(
child: Text(
'Button tapped $_counter time${_counter == 1 ? '' : 's'}.',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
file_io_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:skeet25pro/main_counter.dart';
void main() {
test('Check file save works', () async {
final CounterStorage storage = CounterStorage();
var counter = 6;
var t = await storage.writeCounter(counter);
expect(1, 1);
});
}
When I run the app through a simulator, it works perfectly fine. I would really like to get the tests running.
EDIT: If I try and add WidgetsFlutterBinding.ensureInitialized();
void main() {
test('Check file save works', () async {
WidgetsFlutterBinding.ensureInitialized();
final CounterStorage storage = CounterStorage();
var counter = 6;
var t = await storage.writeCounter(counter);
expect(1, 1);
});
}
I get the error:
MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider_macos)
package:flutter/src/services/platform_channel.dart 175:7 MethodChannel._invokeMethod
Seems like one should use something like: setMockMethodCallHandler to intercept the call to the different directory providers. Still no working solution.
You have to mock the path_provider call and maybe put the WidgetsFlutterBinding.ensureInitialized(); at the beginning of main. I guess you want something like
Future<void> main() async {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
const channel = MethodChannel(
'plugins.flutter.io/path_provider_macos',
);
channel.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'getApplicationDocumentsDirectory':
return "PATH_TO_MOCK_DIR";
default:
}
});
});
test('Check file save works', () async {
final CounterStorage storage = CounterStorage();
var counter = 6;
var t = await storage.writeCounter(counter);
expect(1, 1);
});
}```

Flutter: Notification Navigation from Background Task without context issue

My App does the following: It runs a background Task using Flutter Workmanager which checks some values and then it throws a Notification via Flutter Local Notification. In the initialize method from FlutterLocalNotifications Plugin, i can specify a inline fuction, which should navigate to a page. Since i dont have a Builder context, i must use a Navigator Key with OnGenerateRoute to forward the user to a site. However, this doesn`t work and i don´t know why. I know that this code is useful when the app gotkilled.
Example Code
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
But what to do when the app is still alive? My Project code is listed below.
Main.Dart
void main() {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
Workmanager().registerPeriodicTask("1", "test",frequency: Duration(minutes: 15));
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: "/",
navigatorKey: NavigationService.navigatorKey,
onGenerateRoute: RouteGenerator.generateRoute
);
}
}
RouteGenerator.dart
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch(settings.name) {
case '/first':
return MaterialPageRoute(builder: (_) => Page1(title: "First"));
case '/second':
return MaterialPageRoute(builder: (_) => Page2(title: "Second"));
case '/third':
return MaterialPageRoute(builder: (_) => Page3(title: "Third"));
case '/fourth':
return MaterialPageRoute(builder: (_) => Page4(title: "Fourth"));
}
return MaterialPageRoute(builder: (_) => Page0(title: "Root!"));
}
}
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
static Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
}
service.dart
class DevHttpOverrides extends HttpOverrides {
#override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async{
HttpOverrides.global = new DevHttpOverrides();
var url = 'https://172.16.0.100/handler.php?page=settings';
http.Response response = await http.get(Uri.parse(url));
List<dynamic> list = jsonDecode(response.body);
SharedPreferences prefs = await SharedPreferences.getInstance();
var usage = "Beides";
var checkValue = "temp_out";
var borderValueString = "14.9";
var checktype = "Grenzwert überschreiten";
var borderValueDouble;
var message = "";
if(usage != "Nur Home Widgets" && checkValue != "" && borderValueString != "" && checktype != "")
{
var value = list[0][checkValue];
if (double.tryParse(borderValueString) != null && double.tryParse(value) != null)
{
borderValueDouble = double.parse(borderValueString);
value = double.parse(value);
}
if (checktype == "Grenzwert unterschreiten")
{
if (borderValueDouble is double)
{
if (value <= borderValueDouble)
{
message = "Grenzwert unterschritten";
}
}
}
else if (checktype == "Grenzwert überschreiten")
{
if (borderValueDouble is double)
{
if (value >= borderValueDouble)
{
message = "Grenzwert überschritten";
}
}
}
else if (checktype == "Entspricht Grenzwert")
{
if (borderValueDouble == value)
{
message = "Grenzwert erreicht";
}
}
}
if(message != "")
{
FlutterLocalNotificationsPlugin flip = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var ios = new IOSInitializationSettings();
var settings = new InitializationSettings(android: android, iOS: ios);
flip.initialize(settings, onSelectNotification: (String? payload) async {
await NavigationService.navigatorKey.currentState!.push(MaterialPageRoute(builder: (context) => Page4(title: "Hello")));
});
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'1',
'weatherstation',
'Notify when values change',
importance: Importance.max,
priority: Priority.high
);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics);
await flip.show(0, message,
'App öffnen für weitere Details',
platformChannelSpecifics, payload: 'Default_Sound'
);
}
return Future.value(true);
});
}
Did you find any solution for it? I'm also working on an Android application that update user data in firestore database (at interval of 15min) and send user its notification (both task happens in background using flutter workmanager_plugin). When user taps on the notification he should be navigated to the route which shows latest data from the database.
First 2 background tasks are happening successfully but when the notification is clicked nothing is happening. I'm also using GlobalKey key to get MaterialApp widget's context, so that routing Route can be pushed.
It seems like onSelectNotification property for FlutterLocalNotificationsPlugin.initialize() method don't work for workmanager plugin. I have also added a print statement inside it, but nothing get displayed in console. I thought maybe my Globalkey has some fault but when I tried it for navigating pages in non background task it was happening succesfully, similarly onSelectNotification was working perfectely for non-workmanager task.
void callbackDispatcher() {
Workmanager().executeTask((task, data) async {
if(task=='showNotification'){
FlutterLocalNotificationsPlugin notifPlugin =
FlutterLocalNotificationsPlugin();
NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
'main_channel',
'Main Channel',
'Main Notification Channel',
importance: Importance.max,
priority: Priority.high,
),
);
await notifPlugin.initialize(
InitializationSettings(
android: AndroidInitializationSettings('ic_launcher')),
onSelectNotification: (String? payload) async {
print('Inside on select Route Navigator. Route= $payload');
switch (payload!) {
case 'Home':
// navigatorKey is GlobalKey
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Home()));
break;
case 'Auth':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => Auth()));
break;
case 'Details':
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => UserDetails()));
break;
}
});
await notifPlugin.show(id, title, body, notificationDetails, payload:payload);
}
return Future.value(true);
}
}
When notification is clicked. Following message should print on console: "Inside on select Route Navigator. Route= Details" and he should be navigated on the UserDetails page but nothing seems to be happening.
I solved it by reacting to two different events.
If the App starts, i check if the app was launched by a notification. This can be done in createState or initState in main.dart. This code is useful for that.
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
String initialRoute = HomePage.routeName;
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
If the app is in background and a notification launches the app again, you must use Widgets Binding Observer and react to the App Resume event. There is an article at Medium which has example code for this case. Have a look at it here.
There is one drawback when reacting to App Resume Event and using the Flutter Local notifications Plugin. The aforementioned code always delivers true once triggered by an notification, even if the app entered background state again and was resumed manually by an user. This means code for changing a page will always be called, even
if you did not click an notification. Therefore, I´m using a boolean variable to trigger the App State Resume code once. Obviously, if you enter the app via notification ,the app gets resumed and you get a second notification, the code for changing a page will not be executed. It´s a workaround, but for my case, it´s good enough.

Can't receive object instance from ChangeNotifierProvider

I have this code:
import 'package:flutter/foundation.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:finalprojectapp/Providers/Message_provider.dart';
import 'package:finalprojectapp/Providers/Client_provider.dart';
class MQTTManager with ChangeNotifier{
//Properties
MqttServerClient client;
MQTTMessageProvider messageProvider = MQTTMessageProvider();
String _identifier;
String _topic;
String _host;
//Getters
//Setters
Future initialize({String host, String identifier}) async {
MqttServerClient _client = MqttServerClient(host, identifier);
this._identifier = _client.clientIdentifier;
this._host = host;
_client.port = 1883;
_client.keepAlivePeriod = 20;
_client.onDisconnected = onDisconnected;
_client.onConnected = onConnected;
_client.onSubscribed = onSubscribed;
_client.logging(on: false);
final conMess = MqttConnectMessage()
.withClientIdentifier(identifier)
.keepAliveFor(20)
.withWillTopic('willtopic')
.withWillMessage('willmessage')
.startClean()
.withWillQos(MqttQos.atLeastOnce);
_client.connectionMessage = conMess;
try {
MqttClientConnectionStatus result = await _client.connect('BBFF-qkHkFkvJ6oFUw9m6Pa9bzQTCbVCddH','');
this.client = _client;
notifyListeners();
return result.state;
} on Exception catch (e) {
print('Something went wrong $e');
disconnect();
return null;
}
}
void subscription({String topic}) {
this._topic = topic;
print('EXAMPLE::Subscribing to the $_topic topic');
this.client.subscribe(this._topic, MqttQos.atMostOnce);
}
void unsubscribe({String topic}) {
print('unsubscribing from $topic');
this.client.unsubscribe(topic);
print('Unsubscribbed!');
}
void publish({String topic, String message}) async {
final builder = MqttClientPayloadBuilder();
builder.addString(message);
this.client.publishMessage(topic, MqttQos.atMostOnce, builder.payload);
}
void disconnect() async {
await MqttUtilities.asyncSleep(2);
print('EXAMPLE::Disconnecting');
this.client.disconnect();
}
/// The subscribed callback
void onSubscribed(String topic) {
this._topic = topic;
print('EXAMPLE::Subscription confirmed for topic $this._topic');
this.client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final MqttPublishMessage _recMess = c[0].payload;
final String _message =
MqttPublishPayload.bytesToStringAsString(_recMess.payload.message);
messageProvider.setMessage(_message);
});
}
/// The unsolicited disconnect callback
void onDisconnected() {
print('EXAMPLE::OnDisconnected client callback - Client disconnection');
if (this.client.connectionStatus.returnCode == MqttConnectReturnCode.solicited) {
print('EXAMPLE::OnDisconnected callback is solicited, this is correct');
}
}
/// The successful connect callback
void onConnected() {
print(
'EXAMPLE::OnConnected client callback - Client connection was sucessful');
}
/// Pong callback
void pong() {
print('EXAMPLE::Ping response client callback invoked');
}
}
Which is in charge of notifying when ive obtained a client from Mqtt broker, and is then passed to the client property of this class. When this happens, notifyListeners is supposed to notify this Provider:
class AppWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<MQTTMessageProvider>(
create: (_) => MQTTMessageProvider(),
),
ChangeNotifierProvider<MQTTManager>(
create: (_) => MQTTManager(),
),
],
child: MQTTInitialize()
);
}
}
and then retrieve it in this class ( which is child of MQTTInitialize() ) :
final MQTTManager managerProvider = Provider.of<MQTTManager>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey,
title: Text('Subscribe'),
actions: <Widget>[
FlatButton.icon(
onPressed: (){
managerProvider.disconnect();
},
icon: Icon(Icons.arrow_back),
label: Text('return'),
),
],
),
... (it continues)
Problem is that, when I press the button, it throws the following error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The method 'disconnect' was called on null.
E/flutter (30793): Receiver: null
E/flutter (30793): Tried calling: disconnect()
E/flutter (30793): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
Which I suppose is because managerProvider in final MQTTManager managerProvider = Provider.of<MQTTManager>(context); is null, so I can't call managerProvider.disconnect();
How can I get my ChangeNotifierProvider to provide a correct instance of manager?
Link to project: https://github.com/TacoMariachi/Mqtt_flutter_app.git