How do I pass data to a widget in Flutter? - flutter

I have a json file containing a list of titles and songNumbers. I made a listview and used onTap and Navigator.push to send det data to a detail page. It works fine to display the data as text, but I want to play songs from local assets. I use audioplayers package and if I hardcode a songname it works, but I want to pass the songname into the play function to play the selected song. My code for det detail page look (a bit simplified and shortened) like this:
import 'package:flutter/material.dart';
import 'package:audioplayers/audio_cache.dart';
class SongDetail extends StatelessWidget {
final String title;
final String songNumber;
final player = AudioCache();
SongDetail(this.title, this.songNumber);
#override
Widget build(BuildContext context) {
Widget titleSection = Container(
decoration: BoxDecoration(
color: Colors.blueGrey[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 20.0),
child: IconButton(icon: Icon(
Icons.play_circle_filled,
size: 40.0),
onPressed: () { player.play('1.mp3'); },
),
),
I can use Text(this.title) and Text(this.songNumber) to display the title and songNumber as text. But how to get the data from songNumber into the onPressed function instead of the hardcoded '1.mp3'?
Bear in mind that I am a newbie who maybe has taken a to difficult task, but I like challenges :)

Have you tried this?
onPressed: () { player.play('$songNumber.mp3'); },

Related

Flutter Semantics Reads button title on both single tap and double tap

I have a tooltip in my UI which has semantics label "Tooltip to know about your number". Below is the code snippet for the same.
Semantics(
label: 'Tooltip to know about your number',
child: InkWell(
child: Image.asset('images/info_selected.png'),
onTap: (){
//some action top show tooltip
},
),
),
When accessibility is ON , and I single tap on info Inkwell, it announce "Tooltip to know about your number" as expected. But my issue here , Its also announcing the same when I double tap.. It should only do the functionality which I wrote inside onTap function when I double tap. What is the best way to make it like , it should not announce when I double tap?
Same code is working fine in android and it announce only when I single tap.. only iOS screen reader is announcing the label on both single tap and double tap..
Same issue when I use Gesture Detector or Button instead of InkWell..
Inkwell have a onTap and onDoubleTap both functions available
Reference - https://api.flutter.dev/flutter/material/InkWell-class.html
Output :-
Code :-
import 'package:flutter/material.dart';
class InkwellExample extends StatefulWidget {
const InkwellExample({Key? key}) : super(key: key);
#override
State<InkwellExample> createState() => _InkwellExampleState();
}
class _InkwellExampleState extends State<InkwellExample> {
String taps = "";
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Scaffold(
body: SizedBox(
width: size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
child: const Icon(Icons.info),
onTap: () => setState(() {
taps = "TAP";
}),
onDoubleTap: () => setState(() {
taps = "Double TAP";
}),
),
Text(
taps == "" ? "" : taps,
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}
To keep screen readers from reading anything other than what you have in your label parameter, add excludeSemantics: true. So your code would look like this:
Semantics(
label: 'Tooltip to know about your number',
excludeSemantics: true,
child: InkWell(
child: Image.asset('images/info_selected.png'),
onTap: (){
//some action top show tooltip
},
),
),
Another parameter which may be of interest is onTapHint.
One reference I've used:
https://www.woolha.com/tutorials/flutter-using-semantics-mergesemantics-examples

Flutter : using changeNotifier and provider when the context is not available

I'm trying to use the simple state management described in the Flutter docs, using a ChangeNotifier, a Consumer, and a ChangeNotifierProvider.
My problem is that I can't get a hold a on valid context to update my model (details below...). I get an error:
Error: Error: Could not find the correct Provider above this CreateOrganizationDialog Widget
This likely happens because you used a BuildContext that does not include the provider of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that CreateOrganizationDialog is under your MultiProvider/Provider.
This usually happen when you are creating a provider and trying to read it immediately.
Here are extracts of my code:
class OrganizationModel extends ChangeNotifier {
final List<Organization> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Organization> get items => UnmodifiableListView(_items);
void addList(List<Organization> items) {
_items.addAll(items);
notifyListeners();
}
}
This is my model.
class OrganizationBodyLayout extends StatelessWidget {
Future<void> _showCreateOrganizationDialog() async {
return showDialog<void>(
context: navigatorKey.currentState.overlay.context,
barrierDismissible: false,
child: CreateOrganizationDialog());
}
_onCreateOrganizationPressed() {
_showCreateOrganizationDialog();
}
_onDeleteOrganizationPressed() {
//TODO something
}
_onEditOrganizationPressed() {
//TODO something
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(mainAxisSize: MainAxisSize.max, children: [
ButtonBar(
alignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
RaisedButton(
onPressed: _onCreateOrganizationPressed,
child: Text("New Organization"),
),
],
),
Expanded(
child: Container(
color: Colors.pink,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: ChangeNotifierProvider(
create: (context) => OrganizationModel(),
child: OrganizationListView(),
)),
Expanded(child: Container(color: Colors.brown))
]))),
]));
}
}
A stateless widget that contains a ChangeNotifierProvider just on top of the list widget using the model.
On a button click, a modal dialog is shown, then data is fetched from the network. I should then update my model calling the addList operation.
Below is the code for the stateful dialog box.
class CreateOrganizationDialog extends StatefulWidget {
#override
_CreateOrganizationDialogState createState() =>
_CreateOrganizationDialogState();
}
class _CreateOrganizationDialogState extends State<CreateOrganizationDialog> {
TextEditingController _nametextController;
TextEditingController _descriptionTextController;
#override
initState() {
_nametextController = new TextEditingController();
_descriptionTextController = new TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 200,
height: 220,
child: Column(
children: [
Text('New organization',
style: Theme.of(context).textTheme.headline6),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration: new InputDecoration(hintText: "Organization name"),
controller: _nametextController,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration:
new InputDecoration(hintText: "Organization description"),
controller: _descriptionTextController,
),
),
ButtonBar(
children: [
FlatButton(
child: new Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: new Text("Create"),
onPressed: () {
setState(() {
Future<Organization> organization =
backendCreateOrganization(_nametextController.text,
_descriptionTextController.text);
organization.then((value) {
Future<List<Organization>> organizations =
backendReloadOrganizations();
organizations.then((value) {
var model = context.read<OrganizationModel>();
// var model = navigatorKey.currentState.overlay.context.read<OrganizationModel>();
//model.addList(value);
});
});
});
Navigator.of(context).pop();
//context is the one for the create dialog here
},
)
],
)
],
),
));
}
}
My problem happens at the line
var model = context.read<OrganizationModel>();
Thinking of it, the context available here is the modal dialog box context - so it's kind of logical that the Provider is not found in the widget tree.
However, I can't see how to retrieve the proper context (which would be the one for the result list view, where the Provider is located) in order to get the model and then update it.
Any idea is welcome :-)
Solved (kind of).
The only way I've found to solve this is by making my model a global variable:
var globalModel = OrganizationModel();
And referencing this global model in all widgets that consume it. I can't find a way to find the context of a stateless widget from within a callback in another stateful widget.
It works, but it's ugly. Still open to elegant solutions here :-)
Get_it seems to be elegant way of sharing models across the application. Please check the documentation for the different use cases they provide.
You could do something like the following
GetIt getIt = GetIt.instance;
getIt.registerSingleton<AppModel>(AppModelImplementation());
getIt.registerLazySingleton<RESTAPI>(() =>RestAPIImplementation());
And in other parts of your code, you could do something like
var myAppModel = getIt.get<AppModel>();

Flutter - what is needed to reload a network image when URL doesn't change

I'm just starting to learn app development with Flutter.
I am trying to build an app for learning purposes that grabs an image from a website using their API to get a random image. The app displays the image, and has a button which is supposed to grab a new random image when pressed.
The problem is that the URL for the API doesn't change, so Flutter thinks the image is the same and doesn't download a new image. At least, that's what I think is going on.
The code is very basic. So far just showing an image and a button. I've tried various things, and was thinking imageCache.clear(); should do the job when the button is pushed, but nothing seems to work. I have also tried various tricks with resetting state and trying to navigate to the same page with named routes, but they all still show the same image (which is why I think it might be a caching issue). :shrug:
All of this is running from a single main.dart file.
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
backgroundColor: Colors.lightGreen[50],
appBar: AppBar(
title: Text("Show Me The Lettuce",
style: TextStyle(color: Colors.white70)),
backgroundColor: Colors.green[900],
),
body: ShowLettuce(),
),
),
);
}
class ShowLettuce extends StatefulWidget {
#override
_ShowLettuceState createState() => _ShowLettuceState();
}
class _ShowLettuceState extends State<ShowLettuce> {
String url = 'https://source.unsplash.com/900x900/?lettuce';
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(80.0),
child: Center(child: CircularProgressIndicator()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage, image: url),
),
],
),
SizedBox(
height: 20.0,
),
RaisedButton(
onPressed: () {
imageCache.clear();
print('button pressed.');
},
child: Text('Show Me A New Lettuce!'),
),
],
),
);
}
}
I've read through a lot of Flutter documentation, but I'm not yet at the point where I can interpret much of what they say into usable code (not sure where and how I'm supposed to plug it into my code, for example the imageCache documentation: https://api.flutter.dev/flutter/painting/ImageCache-class.html). It seems like this should be a really easy solution, but it is escaping me.
Any help is much appreciated.
I have solved you problem.
You must add for your code couple lines code:
as first - import:
import 'dart:math';
next - in class _ShowLettuceState after declared url variable:
var random = new Random();
and for finish - after click the button, namely in onPressed:
setState(() {
url = 'https://source.unsplash.com/900x900/?lettuce' + random.nextInt(100).toString();
});
I suggest you use this code :
String url = 'https://source.unsplash.com/900x900/?lettuce-${Random().nextInt(100)}';
This is inspired by #Captivity solution, but I add a - that makes it work.

Flutter snackbar alternative or easier method than wrapping everything in Scaffold?

I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!
But I need to let the user know that the content was copied.
I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?
Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).
import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
class RowRule extends StatelessWidget {
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'],
style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'],
style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
// Scaffold.of(context).showSnackBar(SnackBar
// (content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(
vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
)),
)
],
),
),
));
}
}
The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.
You can use GlobalKey to make it work the way you want it.
Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
key: _key,
body: Column(
children: <Widget>[
Container(
color: Color.fromRGBO(52, 56, 245, 1),
height: 150,
alignment: Alignment.center,
child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 120,
itemBuilder: (context, index) {
return Container(
color: Colors.white,
margin: const EdgeInsets.all(4),
child: ListTile(
title: Text("Row #$index"),
onLongPress: () => _key.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
),
);
},
),
),
],
),
);
}
}
These is a simple plugin replacement for the Snackbar named "Flushbar".
You can get the plugin here - https://pub.dartlang.org/packages/flushbar
You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all.
Inside your onLongPressed in GestureDetectore you can do this.
onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
Flushbar(
message: "Copied !!",
duration: Duration(seconds: 3),
)..show(context);
}
This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.
There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.
Here is the final code (Your way)
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
onPressed: () => print("Handle button press here"),
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
),
),
)
],
),
),
),
),
);
}
}
I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.
I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.
So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
if (!ruleGroup['details'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: ListView(children: _buildChildren()),
);
}
}
I read your comments on all answers and here is my conslusion:
You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.
Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.
Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.

Set fontFamily for all buttons in Flutter

Is there a way to set the fontFamily for all buttons in a Flutter app?
I see I can set my fontFamily for my MaterialApp using theme.fontFamily, but I'd like to use a different fontFamily for all my buttons.
I saw there is also a ButtonThemeData, but it seems to be related to colors and shapes only.
I don't want to set my fontFamily explicitly every time I use a button or having to wrap all types of buttons, is there any way to accomplish this?
Thanks!
You should use themes to customize fonts for whole widgets, including buttons : https://flutter.dev/docs/cookbook/design/fonts
Simplest might be to create a helper method that returns a Button configured as you wish.
I recomend creating a custom button that "extends" Flutter MaterialButton or RawMaterialButton. Remember to add buttonText as a paramater too if you want your button to be reusable. Also remember to add TextStyle(fontFamily: 'Raleway') to the Text widget style.
Another option would be to "extend" the Flutter Text widget in the same way as with the button example below, and add your CustomTextWidget as a child to the Flutter MaterialButton widget. I prefer to use both in a combination. CustomButton together with CustomText widget.
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
CustomButton({#required this.onPressed});
final GestureTapCallback onPressed;
#override
Widget build(BuildContext context) {
return RawMaterialButton(
fillColor: Colors.green,
splashColor: Colors.greenAccent,
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Icon(
Icons.face,
color: Colors.amber,
),
SizedBox(
width: 10.0,
),
Text(
"Tap Me",
maxLines: 1,
style: TextStyle(color: Colors.white),
),
],
),
),
onPressed: onPressed,
shape: const StadiumBorder(),
);
}
}
Here is the implementation:
CustomButton(
onPressed: () {
print("Tapped Me");
},
)