I have this snackbar code where I can show a message to the user
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
showSnackBar(String text, Color color) {
final snackBar = new SnackBar(
content: CustomText(
text: text,
color: white,
weight: FontWeight.bold,
size: 17,
),
duration: Duration(seconds: 4),
backgroundColor: color,
);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
and inside my scaffold I added my key as key: _scaffoldKey, And then I can call the snackbar using the code showSnackBar("Some Text", Colors.red) But the problem I have is that I have to add this code in every page/screen that am using
So what I want is to create a separate dart file and add this code then be able to import and use in any page I want. So please how do I do That.
N/B: Please Ignore the CustomText In my code cause that's a model-ish I use
I came up with a simple solution, you can make a static function for snackbar and use it all through the app just by passing a scaffoldkey and the message like following
class MyMessageHandler {
static void showMySnackBar(var _scaffoldKey, String message) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
backgroundColor: Colors.green,
content: Text(message ?? ""),
duration: Duration(seconds: 2),
));
}
}
and then call it with any event triggering widget like following
RaisedButton(
onPressed: () =>
MyMessageHandler.showMySnackBar(_scaffoldKey, "message"),
child: Text("Show my snackbar"),
);
Now you have your custom snackbar, you can modify it according to your design :)
here is snippet extracted from my apps.
step.1: create a dart file called common_dialogs.dart
import 'package:flutter/material.dart';
// ...
theSnackBar(context, String message) {
return SnackBar(
duration: Duration(seconds: 2),
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 260.0,
child: Text(
message,
overflow: TextOverflow.fade,
softWrap: false,
),
),
Icon(Icons.error),
],
),
backgroundColor: Colors.orangeAccent,
);
}
step.2: any widget that needs those widgets, then it must import 'common_dialogs.dart'; first
step.3: now you can call show snack bar like this
_scaffoldKey.currentState
..hideCurrentSnackBar()
..showSnackBar(theSnackBar(context, message));
Here is a clean approach: NOTE: you're going to depend on context
Create my_util.dart and:
extension MyUtils on BuildContext {
void showErrorMessage(String error, {int? duration}) {
ScaffoldMessenger.of(this)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Row(
children: [
Icon(Icons.warning_rounded, color: Colors.white),
SizedBox(
width: 10,
),
Flexible(
child: Text(
error,
overflow: TextOverflow.clip,
),
),
],
),
dismissDirection: DismissDirection.startToEnd,
behavior: SnackBarBehavior.fixed,
duration: Duration(seconds: duration ?? 2),
),
);
}
}
Import my_util.dart
ElevatedButton(onPressed: () {
context.showErrorMessage('Convert your money to naira to continue');
});
I hope it helps.
Related
I have a DataTable in which some cells have links. Ideally, I would like to fetch a preview about the link's content whenever hovering over the link, which I was able to achieve using the Stack widget. However, since the stacked preview is inside the DataCell, it seems like I'm not able to raise its "z-index" to be on top of the rest of the table.
Is this not possible with Flutter, or is there a way around it?
The only way I imagine this working, without something to update a global z-index, would be for the cell to update a global state and then have the thumbnail preview appear on a Stack above the DataTable level. But I wish there was a less clunkier way to do it...
3 widgets I've tried but to no avail — they might work, I don't know —:
Tooltip
Overlay
FloatingActionButton
My whole app is here, and the precise commit is 0303732. The relevant code is this ClickableLink widget:
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
import '../schema/links.dart';
#immutable
class ClickableLink extends StatefulWidget {
const ClickableLink({
Key? key,
required this.link,
this.linkText,
this.color = Colors.blue,
}) : super(key: key);
final Link link;
final String? linkText;
final Color color;
#override
State<ClickableLink> createState() => _ClickableLinkState();
}
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
setState(() {
if (widget.link.host == 'online-go.com' && widget.link.prePath == 'game') {
hoverWidget = Positioned(
top: 25,
child: Image.network('https://online-go.com/api/v1/games/${widget.link.id}/png'),
);
}
});
}
void _onExit(PointerEvent pointerEvent) {
setState(() {
hoverWidget = const SizedBox.shrink();
});
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
onExit: _onExit,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(color: widget.color),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
hoverWidget,
],
),
);
}
}
The problem here is due to the fact that your Stack widget, defined inside ClickableLink, will be at a "lower" point (inside your app widget tree) than every other GameResultCell.
So even the higher z-index will still be behind the other GameResultCells.
To fix this I would reccomend changing your structure and define an higher point in your structure to show the preview.
Another way could be using a library to nest your preview inside a tooltip. Take a look at this one for example:
just_the_tooltip: ^0.0.11+2. With this package, you could even use a StatelessWidget.
The result here is more similar to what I suppose you were expecting.
class ClickableLink extends StatelessWidget {
#override
Widget build(BuildContext context) {
return JustTheTooltip(
content: Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png',
),
child: SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
);
}
}
Lastly you could use a Dialog, but the resulting behaviour is a bit different.
Take a look at this code if you want to try:
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png'),
const SizedBox(
height: 16.0,
),
TextButton(
onPressed: () async => launch(widget.link.completeLink),
child: const Text('Go to complete link'))
],
),
);
},
);
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
],
),
);
}
}
I have program where I need to add a count next to a bottom navigation icon. So, I am trying to use the ChangeNotifierProvider and wrap it around my root widget. How do I get the count from the IterableModel to my widget?
MAIN.APP
ChangeNotifierProvider(create: (_) => IterableModel()),
HOME.DART
BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(top: 20.0),
child: ImageWithBadge(
text: 'notification',
iconData: Icons.mail,
notificationCount: "NEED MESSAGE COUNT,
),
),
),
IterableModel.dart
class IterableModel extends ChangeNotifier {
List<InAppMessages> inAppMessages;
int cnt;
IterableModel({this.inAppMessages});
void messageCnt(List inAppMessages) {
cnt = inAppMessages.length;
notifyListeners();
}
IterableModel.fromJson(Map<String, dynamic> json) {
if (json['inAppMessages'] != null) {
inAppMessages = [];
json['inAppMessages'].forEach((v) {
inAppMessages.add(new InAppMessages.fromJson(v));
});
messageCnt(inAppMessages);
}
}
}
I think i need a consumer and builder but I don't now where to place it
final count = Provider.of<IterableModel>(context).cnt;
Insert this line at the top of your build method than it would be accessible but also rebuild the entire widget one better aproach is to use Consumer
Consumer<IterableModel>(
builder: (context,model,_) => BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(top: 20.0),
child: ImageWithBadge(
text: 'notification',
iconData: Icons.mail,
notificationCount: model.cnt.toString(),
),
),
),
)
I'm using a listview to display images and text in my flutter app. I've stored the asset path and text in a Json file and I convert it to a list. Getting the image asset path and displaying the correct one seems to work with no issues but thats not the case in playing the audio files from their assets.
I'm using this package import 'package:assets_audio_player/assets_audio_player.dart';
declaration final AssetsAudioPlayer playAudio = AssetsAudioPlayer();
and this is main widget
#override
Widget build(BuildContext context) {
Widget _buildRow(int idx) {
for (var translations in widget.category.translations) {
_wordList = widget.category.translations[idx];
return Container(
height: 88.0,
child: Card(
child: ListTile(
onTap: () {
playAudio.open(
Audio(_wordList.audio),
);
// player.play(_wordList.audio);
log(_wordList.audio, name: 'my.other.category');
},
onLongPress: () {},
leading: SizedBox(
width: 50.0,
height: 88.0,
child: Image(
image: AssetImage(_wordList.emoji),
fit: BoxFit.contain,
),
),
title: Text(
_wordList.akan,
style: TextStyle(fontSize: 18),
),
subtitle: Text(
_wordList.english,
style: TextStyle(fontSize: 18, color: Colors.black),
),
trailing: const Icon(Icons.play_arrow, size: 28),
),
),
);
}
}
Since the image assets in the json file have no issues I don't get why the audio does
I've stored them like this,
{
"english": "mother",
"akan": "ɛna",
"emoji": "assets/icons/family_mother.png",
"audio": "assets/audio/family_mother.mp3"
},
Solved it by generating a new listtile widget through iteration and then putting it into a listview
I have implemented an listener in the following way:
#override
void didChangeDependencies() {
final SlotDataProvider slotDevice = Provider.of<SlotDataProvider>(context);
spptask.streamdevice.listen((device) {
setState(() {
slotDevice._devices[0].name = device.name;
print("Device data received: ${device.name} ");
});
}, onError: (error) {
print("Error: $error.message");
});
super.didChangeDependencies();
}
I listen on a splitted controller and the print "Device data received:..." is called but the widget is not actualized. In the build method I do the following:
...
#override
Widget build(BuildContext context) {
final slotProvider = Provider.of<SlotDataProvider>(context);
final deviceProvider = Provider.of<DeviceDataProvider>(context);
Device slotDevice = slotProvider.getDevice(widget.slot);
Device device = deviceProvider.getDevice(widget.slot);
_dropdownMenuItems = buildDropdownMenuItems(deviceProvider.get());
return ListTile(
title: Row(
children: <Widget>[
SizedBox(
width: 140,
child: DropdownButton(
isExpanded: true,
disabledHint: Text(slotDevice.name),
hint: Text(slotDevice.name),
value: device,
items: _dropdownMenuItems,
onChanged: (value) {
device.setDevice(value);
slotDevice.setDevice(value);
}),
),
SizedBox(width: 10),
SizedBox(width: 60, child: Text('SLOT#${slotDevice.slot}')),
],
),
subtitle: Text(slotDevice.bdaddr, style: TextStyle(fontSize: 10.0)),
leading: SizedBox(
height: 40,
width: 35,
child: UsbBatteryImageAsset(slot: widget.slot),
),
trailing: Icon(Icons.keyboard_arrow_right),
);
}
}
What is missing in the above code. The SlotDataProvider is a fix list of "Device" with attributes such as name, id and so on.
#EDIT
The problem has to do with the combobox. If I change an other field, it works.
Usually for widgets to be rebuilt based on the data updated we use streambuilders
this will cause the widget to rebuild every time there is a change in the stream
it seams that your widget is being built once with the first listening of the data
have you tried wrapping the gridview in a stateful builder ?
This project can be found here:https://github.com/Santos-Enoque/flutter_blog_app
So far I have this connected to firebase realtime database and it works well. I'm trying to add a like button to the Home page(lib/screens/home.dart) where all the posts are listed.
The Homepage displays the blog results using a Card with a ListTile. The trailing property of the ListTile card is already used so I'd like to use the leading property of the ListTile card to display a favourite icon which would increment a counter++ when tapped and also save the results to Firebase. Just like Facebook's like button.
Here's the code below:
child: Card(
child: ListTile(
title: ListTile(
onTap: (){
_incrementCounter();
},
leading: FittedBox(
fit: BoxFit.fitWidth,
child: Row(
children: <Widget>[
Icon(Icons.favorite),
Text(postsList[index].counter.toString()
),
],
),
),
title: Text(
postsList[index].title,
style: TextStyle(
fontSize: 16.0, fontWeight: FontWeight.bold),
),
trailing: Text(
timeago.format(DateTime.fromMillisecondsSinceEpoch(postsList[index].date)),
style: TextStyle(fontSize: 12.0, color: Colors.grey),
),
),
subtitle: Padding(
padding: const EdgeInsets.only(bottom: 14.0),
child: Text(postsList[index].body, style: TextStyle(fontSize: 14.0),),
),
),
),
Here's the _increment counter code:
try {
var ref = FirebaseDatabase.instance.reference().child('posts/{postId}/counter');
await ref.once().then((data) async => {
await ref.set(data.value + 1),
});
} catch (e) {
print(e.message);
}
}
![Home page of blog]https://photos.google.com/share/AF1QipMK6C-Wx2vZHHbE2jDMQsfYNnwl9OWK_5W8OKOfiIChcXt-gnWnCH7ba_EpyRnRAA?key=cGxkRkVSSk9PQTdtTXB0MzZBRDNHNUVzSGxlcDVB
The blog posts are displayed as cards as in the image ... I'm trying to add an icon on the left side of the card(leading) plus an incrementing value everytime someone taps the icon. Something like the like button on facebook. And also save the data to firebase realtime database.
Any help is much appreciated ... Thank you all!
I think what you may want to do is add this function to your onPressed. You will also need to set the text of the next to the icon equal to the new value read.
void like() async {
try {
var ref = FirebaseDatabase.instance.reference().child('path to likes for a post');
await ref.once().then((data) async => {
await ref.set(data.value + 1);
});
} catch (e) {
print(e.message);
}
}
Hope this helps.
P.S.- You may find this video of use: https://www.youtube.com/watch?v=l8_7RTRRmHo
Thanks to everyone who helped with this:
Code for UI:
leading: FittedBox(
fit: BoxFit.fitWidth,
child: Row(
children: <Widget>[
Icon(Icons.favorite),
Text(postsList[index].counter.toString()
),
],
),
),
Code for Function:
onTap: (){
_incrementCounter(postsList[index].key);
}
...
void _incrementCounter(key) async {
try {
var ref = FirebaseDatabase.instance.reference().child('posts/'+ key +'/counter');
await ref.once().then((data) async => {
await ref.set(data.value + 1),
});
} catch (e) {
print(e.message);
}
}
}