I have built an Android app with Flutter, and integrated Firebase Messaging on the app.
I've been having a problem where if an Android device receives a notification while the app is closed; that notification will launch onLaunch infinitely.
I opened an issue github::flutter/flutter/issues/18524, but was hoping that someone has solved the problem (or knows the cause).
I've tried moving the code below around, but still see the same result. Even after a fresh install on a device, I still see the issue.
Has anyone come across this before?
#override
void initState() {
super.initState();
/// Navigate to item based on message type
///
void _navigateToItemDetail(Map<String, dynamic> message) {
if (message.containsKey("type")) {
// handle messages by type
String type = message["type"];
String _id = message["_id"] ?? "";
switch (type) {
case "private_message":
Application.router.navigateTo(context, "/private_message/$_id");
break;
case "announcement":
FlutterWebBrowser.openWebPage(
url: message["url"] ?? "https://me.app",
androidToolbarColor: Colors.red
);
break;
case "public_message":
Application.router.navigateTo(context, "/public_message/$_id");
break;
default:
}
}
}
Future<Null> _showItemDialog(Map<String, dynamic> message, BuildContext ctx) async {
return showDialog<Null>(
context: ctx,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return new AlertDialog(
title: new Text(
'New Notification!',
style: new TextStyle(
fontWeight: FontWeight.w800,
)
),
content: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
new Text(message["summary"] ?? "You have a message"),
],
),
),
actions: <Widget>[
new FlatButton(
textColor: Colors.red[900],
child: new Text(
"View",
style: new TextStyle(fontFamily: "Roboto")
),
onPressed: () {
_navigateToItemDetail(message);
Navigator.pop(context);
}
),
new FlatButton(
textColor: Colors.red[900],
child: new Text('Ignore', style: new TextStyle(fontFamily: "Roboto")),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
).then((shs) {
print("$shs results");
});
}
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) {
print("onMessage: $message");
_showItemDialog(message, context);
},
onLaunch: (Map<String, dynamic> message) {
print("onLaunch: $message");
_navigateToItemDetail(message);
},
onResume: (Map<String, dynamic> message) {
print("onResume: $message");
_navigateToItemDetail(message);
},
);
}
The issue wasn't with Firebase Messaging.
For anyone else who finds themselves here:
I have a BottomNavigation that is based on the demo in Flutter. I was navigating to the same BottomNavigation, and that was causing an infinite navigation loop.
Related
I using qr_code_scanner for scan a qr code & barcodes. Its scanning perfectly. But when i want to use dialog after scan for ask how much product did you scan and after that check it and control it.But when i use textfield inside of dialog and when i tap textfield camera stops working and its stays in black screen. What should i do ? What is wrong ? My codes for scan :
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
player.play("scanner_sound.mp3");
inspect(args);
if (args.Barcode == scanData.code) {
showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context);
controller.resumeCamera();
return true;
},
child: AlertDialog(
title: const Text('Ürün Giriş'),
content: Column(
children: [
const Text('Bu üründen kaç adet okutuldu ?'),
TextField(
keyboardType: TextInputType.number,
controller: _controller,
),
],
),
actions: <Widget>[
TextButton(
onPressed: () {
bool isTrue = checkScannedCount(int.parse(_controller.text));
if (isTrue) {
var model = args.copyWith(ScannedCount: args.Count);
context.read<ProductCubit>().updateProduct(model);
Navigator.pop(context);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Lütfen sayımı tekrarlayınız."),
),
);
Navigator.pop(context);
controller.resumeCamera(); //Its not starting camera again.
}
},
child: const Text('Tamam')),
],
),
);
});
} else {
showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context);
controller.resumeCamera();
return true;
},
child: AlertDialog(
title: const Text('Hatalı Barkod veya Ürün'),
content: const Text('Yanlış ürünü veya barkodu okutuyor olabilirsiniz. Kontrol edip tekrar ediniz.'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
controller.resumeCamera();
},
child: const Text('Tamam')),
],
),
);
});
}
});
}
qr_code_scanner no longer supported . Since the underlying frameworks of this package, zxing for android and MTBBarcodescanner for iOS are both not longer maintaned . use mobile_scanner
I am using Agora for a one-to-one video chat purpose in Flutter.
User1 has an app to go online and user2 has another app to go online. After both of them go online, they can do video chat with one another. Both apps have almost similar codebase.
I have a screen or activity (say screen1) where an alert dialog is shown on tapping a button (say button1). On tapping the Continue button in the alert dialog, the dialog disappears and the user is taken to the screen (say screen2) where the video chat takes place. But after going to the video chat screen successfully, if the user taps on the back button on the mobile set then s/he is taken to screen1 and after tapping on button1, if the user taps on the Continue button in the popped up alert dialog, the user is again taken to screen2 but this time the local video (i.e. video of the user using the app) keeps loading for ever. Obviously I want the local video to load as it did for the first time.
I am gonna put my code here in such a way that you can easily run that.
Following code is for user1. For user2, no alert box is there in the app. Same code from user1 is used for user2 app, except the value of remoteUid is set to be 2 for user2 while this value is set to be 1 for user1. These are just two values identifying 2 users.
For user1:
main.dart:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'livesession1to1.dart';
void main() {
runApp(MessagingExampleApp());
}
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Messaging Example App',
navigatorKey: NavigationService.navigatorKey, // set property
theme: ThemeData.dark(),
routes: {
'/': (context) => Application(),
'/liveSession1to1': (context) =>LiveSession1to1(),
},
);
}
}
int _messageCount = 0;
/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token, String server_key) {
_messageCount++;
return jsonEncode({
'token': token,
'to':token,
'data': {
'via': 'FlutterFire Cloud Messaging!!!',
'count': _messageCount.toString(),
},
'notification': {
'title': 'Hello FlutterFire!',
'body': 'This notification (#$_messageCount) was created via FCM! =============',
},
"delay_while_idle" : false,
"priority" : "high",
"content_available" : true
});
}
/// Renders the example application.
class Application extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
String? _token;
#override
void initState() {
super.initState();
}
showAlertDialog() {
BuildContext context=NavigationService.navigatorKey.currentContext!;
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
onPressed: () {},
);
Widget continueButton = TextButton(
child: Text("Continue"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context).pushNamed('/liveSession1to1');
},
);
Timer? timer = Timer(Duration(milliseconds: 5000), (){
Navigator.of(context, rootNavigator: true).pop();
});
showDialog(
context: context,
builder: (BuildContext builderContext) {
return AlertDialog(
backgroundColor: Colors.black26,
title: Text('One to one live session'),
content: SingleChildScrollView(
child: Text('Do you want to connect for a live session ?'),
),
actions: [
cancelButton,
continueButton,
],
);
}
).then((value){
// dispose the timer in case something else has triggered the dismiss.
timer?.cancel();
timer = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My App'),
),
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(
onPressed: showAlertDialog,
backgroundColor: Colors.white,
child: const Icon(Icons.send),
),
),
body: SingleChildScrollView(
child: Text(
'Trigger Alert'
),
),
);
}
}
livesession1to1.dart:
import 'dart:async';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;
import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
// const appId = "<-- Insert App Id -->";
// const token = "<-- Insert Token -->";
const appId = "......";// Put Agora App ID from Agora site here
const token = "....";// Put token ( temporary token avilable from Agora site)
void main() => runApp(MaterialApp(home: LiveSession1to1()));
class LiveSession1to1 extends StatefulWidget {
#override
_LiveSession1to1State createState() => _LiveSession1to1State();
}
class _LiveSession1to1State extends State<LiveSession1to1> {
int _remoteUid=1;
bool _localUserJoined = false;
late RtcEngine _engine;
#override
void initState() {
super.initState();
setState(() {});
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
// Create RTC client instance
RtcEngineContext context = RtcEngineContext(appId);
_engine = await RtcEngine.createWithContext(context);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
try {
await _engine.joinChannel(token, "InstaClass", null, 0);
} catch (e) {
print("error with agora = ");
print("$e");
print("error printeddddd");
}
}
// Create UI with local view and remote view
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
print("'Please wait for remote user to join',");
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
For user2:
main.dart:
import 'dart:async';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;
import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
const appId = "....."; // Same as user1 app
const token = "....."; // same as user1 app
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// int? _remoteUid=1;
int _remoteUid=2;
bool _localUserJoined = false;
late RtcEngine _engine;
#override
void initState() {
super.initState();
initAgora();
}
Future<void> initAgora() async {
// retrieve permissions
await [Permission.microphone, Permission.camera].request();
//create the engine
_engine = await RtcEngine.create(appId);
await _engine.enableVideo();
_engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (String channel, int uid, int elapsed) {
print("local user $uid joined");
setState(() {
_localUserJoined = true;
});
},
userJoined: (int uid, int elapsed) {
print("remote user $uid joined");
setState(() {
_remoteUid = uid;
});
},
userOffline: (int uid, UserOfflineReason reason) {
print("remote user $uid left channel");
setState(() {
// _remoteUid = null;
_remoteUid = 0;
});
},
),
);
// await _engine.joinChannel(token, "test", null, 0);
await _engine.joinChannel(token, "InstaClass", null, 0);
}
// Create UI with local view and remote view
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Video Call'),
),
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 100,
height: 150,
child: Center(
child: _localUserJoined
? RtcLocalView.SurfaceView()
: CircularProgressIndicator(),
),
),
),
],
),
);
}
// Display remote user's video
Widget _remoteVideo() {
/*if (_remoteUid != null) {
return RtcRemoteView.SurfaceView(uid: _remoteUid!);
}*/
if (_remoteUid != 0) {
return RtcRemoteView.SurfaceView(
uid: _remoteUid,
channelId: "InstaClass",
);
}else {
return Text(
'Please wait for remote user to join',
textAlign: TextAlign.center,
);
}
}
}
In order to get the app ID and token, login to Agora site. After logging in, go to the 'Project Management' section to see the projects already created there. Under the Functions column, click on the key symbol and you will be taken to a page where you can generate a temporary token. On that page, give the channel name input the value 'InstaClass' as I have used this name in my code.
How to make the video chat work smoothly after the first time it works well ?
I think the problem is that when pressing back button you are just being taken to the previous screen and the call session is not being end. You can try by leaving the channel when pressing back button like :
_engine.leaveChannel();
End Call button sample
ElevatedButton(
onPressed: () {
_rtcEngine.leaveChannel();
Navigator.pop(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.all(CircleBorder()),
backgroundColor: MaterialStateProperty.all(Colors.red),
padding: MaterialStateProperty.all(
EdgeInsets.fromLTRB(15, 15, 15, 12)),
),
child: Icon(
Icons.phone,
size: 30,
),
)
Back Button override using WillPopScope
return WillPopScope(
onWillPop: () async {
_rtcEngine.leaveChannel();
return true;
},
child: Scaffold(
body: Container(),
),
);
i try do example flutter app get notification from firebase. I have done everything suggested in the various guides. but I'm in trouble, in ios the code works perfectly, while on android it doesn't and I can't explain why. It seems that when _firebaseMessaging.getToken () is executed it leads nowhere and waits for a response that never comes. some of you have some tips? I tried all the functions made available by _firebaseMessaging but without success.
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
class PushMessagingExample extends StatefulWidget {
#override
_PushMessagingExampleState createState() => _PushMessagingExampleState();
}
class _PushMessagingExampleState extends State<PushMessagingExample> {
String _homeScreenText = "Waiting for token...";
String _messageText = "Waiting for message...";
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
#override
void initState() {
debugPrint('test firebase');
super.initState();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
setState(() {
_messageText = "Push Messaging message: $message";
});
print("onMessage: $message");
},
onLaunch: (Map<String, dynamic> message) async {
setState(() {
_messageText = "Push Messaging message: $message";
});
print("onLaunch: $message");
},
onResume: (Map<String, dynamic> message) async {
setState(() {
_messageText = "Push Messaging message: $message";
});
print("onResume: $message");
},
);
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true));
_firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
_firebaseMessaging.subscribeToTopic('all');
debugPrint('token call');
_firebaseMessaging.getToken().then((String token) {
debugPrint('test firebase token: $token');
print(token);
assert(token != null);
setState(() {
_homeScreenText = "Push Messaging token: $token";
});
print(_homeScreenText);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Push Messaging Demo'),
),
body: Material(
child: Column(
children: <Widget>[
Center(
child: Text(_homeScreenText),
),
Row(children: <Widget>[
Expanded(
child: Text(_messageText),
),
])
],
),
));
}
}
void main() {
runApp(
MaterialApp(
home: PushMessagingExample(),
),
);
}
I think your problem is an asynchronous method, first, divide them into Future methods and into methods to create different FirebaseMessaging instances.
I am trying to write a program to check if the time selected by the user already exists in the firebase firestore or not. If it does then I navigate back to the page where they select time again.
But as of now, I am succeeded in sending the date and time to firebase and but not the latter part.
DateTime _eventDate;
bool processing;
String _time;
bool conditionsStatisfied ;
#override
void initState() {
super.initState();
_eventDate = DateTime.now();
processing = false ;
}
inside showDatePicker()
setState(() {
print('inside the setState of listTile');
_eventDate = picked ;
});
inside the button (SAVE):
onPressed: () async {
if (_eventDate != null) {
final QuerySnapshot result = await FirebaseFirestore
.instance
.collection('events')
.where('event_date', isEqualTo: this._eventDate)
.where('selected_time', isEqualTo: this._time)
.get();
final List <DocumentSnapshot> document = result.docs;
if (document.length > 0) {
setState(() {
print('inside the method matching conditions');
showAlertDialogue(context);
});
}else{
final data = {
// "title": _title.text,
'selected_time ': this._time,
"event_date": this._eventDate
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
};
some guidance needed on how do I resolve this issue!
Also, because of the else statement now the program won't write the date into firestore.
After Alot of research, I came to realize that if you send the data from calendar in DateTime format then, because of the timestamp at the end of the Date it becomes impossible to match to dates. Hence I formatted the DateTime value into (DD/MM/YYYY).
Here is the rest of the code for reference:
class _AddEventPageState extends State<AddEventPage> {
String _eventDate;
bool processing;
String _time;
#override
void initState() {
super.initState();
// _eventDate = DateTime.now();
processing = false ;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Please select a date'),),
body: Column(
children: [
hourMinute30Interval(),
Text('$_time'),
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
ListTile(
title: Text(
'$_eventDate'),
onTap: () async {
DateTime picked = await showDatePicker(context: context,
initialDate: DateTime.now(),
firstDate: DateTime(DateTime.now().year - 1),
lastDate: DateTime(DateTime.now().year + 10),);
if (picked != null) {
setState(() {
print('inside the setState of listTile');
_eventDate = DateFormat('dd/MM/yyyy').format(picked) ;
});
}
},
),
SizedBox(height: 10.0),
ListTile(
title: Center(
child: Text('Select time for appointment!', style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
),
processing
? Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
elevation: 5.0,
borderRadius: BorderRadius.circular(30.0),
color: Theme
.of(context)
.primaryColor,
child:MaterialButton(
child: Text('SAVE', style: TextStyle(
fontSize: 20,
color: Colors.white,
fontWeight: FontWeight.bold,
)),
onPressed: () async {
if (_eventDate != null) {
AddingEventsUsingRajeshMethod().getAvailableSlots(
_eventDate, _time).then((QuerySnapshot docs) async {
if (docs.docs.length == 1) {
showAlertDialogue(context);
}
else{
final data = {
// "title": _title.text,
'selected_time': this._time,
"event_date": _eventDate,
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
});
}
}
),
),
),
],
),
],
),
);
}
showAlertDialogue method :
showAlertDialogue(BuildContext context) {
Widget okButton = FlatButton(onPressed: (){
Timer(Duration(milliseconds: 500), () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => datePicker()),
);
});
}, child: Text(' OK! '));
AlertDialog alert = AlertDialog(
title: Text('Slot unavailable'),
content: Text('This slot is already booked please select another slot'),
actions: [
okButton,
],
);
showDialog(context: context ,
builder: (BuildContext context){
return alert ;
}
);
}
The hourMinute30Interval() is nothing but a Widget that returns a timePickerSpinner which is a custom Widget. Tap here for that.
The Query that is run after passing the _eventDate and _time is in another class, and it goes as follows :
class AddingEventsUsingRajeshMethod {
getAvailableSlots(String _eventDate , String _time){
return FirebaseFirestore.instance
.collection('events')
.where('event_date', isEqualTo: _eventDate )
.where('selected_time', isEqualTo: _time)
.get();
}
}
You can name it something prettier ;)
I have implemented a function to form submitting.I want to have SnackBar Alert to after submitted. I have tried but it doesn't work.After I added SnackBar routing also doesn't work.
addTicket() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
try{
DocumentReference ref = await db.collection('CostalLineTicketDetails').
document(ticketCato).collection("Tickets").add(
{
'startStation':startStation,
'endStation':endStation,
'price':price,
'ticketType':ticketCato,
'contactNo':contactNo,
'dateTime':dateTime,
});
setState(() => id = ref.documentID);
Navigator.push(context, new MaterialPageRoute(builder: (context) => CostalLine()));
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Ticket Added Sucessfully')));
}catch(e){
print(e);
}
}
}
}
You cannot show showSnackBar on same page after going to another screen.
You can declare _scaffoldKey and pass it to Scaffold like this
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Scaffold(
key: _scaffoldKey,
then open snackbar like this
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(
'Welcome',
),
duration: Duration(seconds: 2),
));
Output:
Edit
You can also use flash where you don't need to pass _scaffoldKey every time.
example:
void _showBasicsFlash({
Duration? duration,
flashStyle = FlashBehavior.floating,
}) {
showFlash(
context: context,
duration: duration,
builder: (context, controller) {
return Flash(
controller: controller,
behavior: flashStyle,
position: FlashPosition.bottom,
boxShadows: kElevationToShadow[4],
horizontalDismissDirection: HorizontalDismissDirection.horizontal,
child: FlashBar(
content: Text('This is a basic flash'),
),
);
},
);
}
try this,
addTicket() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
try{
DocumentReference ref = await
db.collection('CostalLineTicketDetails').
document(ticketCato).collection("Tickets").add(
{
'startStation':startStation,
'endStation':endStation,
'price':price,
'ticketType':ticketCato,
'contactNo':contactNo,
'dateTime':dateTime,
});
setState(() => id = ref.documentID);
// Navigator.push(context, new MaterialPageRoute(builder: (context) => CostalLine()));
Scaffold.of(context).showSnackBar(SnackBar(content:
Text('Ticket Added Sucessfully')));
}catch(e){
print(e);
}
}
}
}
Define this code in any of the generalized dart file, and you can call this function at any place and will display a generic type scaffold.
import 'package:flutter/material.dart';
void showWarningSnackBar(BuildContext context, String message) {
// Find the Scaffold in the widget tree and use it to show a SnackBar.
ScaffoldFeatureController<Widget, dynamic> _scaffold;
// Find the Scaffold in the widget tree and use it to show a SnackBar.
_scaffold = Scaffold.of(context).showSnackBar(SnackBar(
content: InkWell(
onTap: () {
_scaffold.close();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
AppImage.asset(
assetName: YOUR_IMAGE_NAME,
fit: BoxFit.contain,
width: 20,
color: COLOR),
const SizedBox(
width: 10,
),
Text(
'$message',
maxLines: 2,
),
],
),
),
duration: const Duration(seconds: 10),
backgroundColor: COLOR,
));
}