Flutter GridView not showing when running on server - flutter

Here is my the GridView that I use in my app when I run it locally :
And here when I make build (flutter build web) and run it on a server :
I get this error in the browser's console:
TypeError: Instance of 'minified:jO': type 'minified:jO' is not a
subtype of type 'minified:fC' main.dart.js:11277 at Object.c
(http://localhost/web/main.dart.js:10095:3) main.dart.js:11277 at
Object.az8 (http://localhost/web/main.dart.js:10740:18)
main.dart.js:11277 at iR.aMx [as a]
(http://localhost/web/main.dart.js:10735:3) main.dart.js:11277 at
eR.tq (http://localhost/web/main.dart.js:63249:6) main.dart.js:11277
at tE.xS (http://localhost/web/main.dart.js:66379:58)
main.dart.js:11277 at tE.ep
(http://localhost/web/main.dart.js:66299:3) main.dart.js:11277 at
tE.ep (http://localhost/web/main.dart.js:66408:3) main.dart.js:11277
at eD.ul (http://localhost/web/main.dart.js:66063:3)
main.dart.js:11277 at eD.eu
(http://localhost/web/main.dart.js:66023:16) main.dart.js:11277 at
eD.iZ (http://localhost/web/main.dart.js:66186:32)
edit : I added the code of CardTest
Here is the code of my GridView :
Column(
children: [
buildSubheadingText('Mes projets'),
buildVerticalSpace(5.0),
GridView.count(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
childAspectRatio: kIsWeb ? 4/1.3 : 1 /1.4,
//physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
children: [
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
),
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
),
CardTest(
loadingPercent: 0.25,
title: 'Making History Notes',
subtitle: '20 hours progress',
dueDate: DateTime(2022, 4, 12)
)
)
],
);
Here is the code of CardTest widget :
import 'package:flutter/material.dart';
class CardTest extends StatelessWidget {
final double loadingPercent;
final String title;
final String subtitle;
final DateTime dueDate;
CardTest({
required this.loadingPercent,
required this.title,
required this.subtitle,
required this.dueDate
});
#override
Widget build(BuildContext context) {
return Flexible(
child: Container(
color: Colors.amber,
child : Column(
children: [
Text(loadingPercent.toString()),
Text(title),
Text(subtitle),
Text(dueDate.toString()),
],
),
),
);
}
}

From #MarianoZorrilla in the comments :
I simply had to remove the Flexible from my CardTest.

Related

How to get the index value whilst not getting duplicates within ListView.builder?

I'm trying to show the week's budget spending & timeline only when the user has put in the spending information.
This is the expected result and all seems to be working nicely until I add in more than just one spending in each week.
Here's what happens:
The problem that I understand is that the ListView.builder gets the date that is between "Initial" and "End" and builds the widgets. So because there's 2 spending between those dates, then it builds 2 of those widgets. The problem is I just can't seem to figure out a way to show them without duplication.
Here's my code:
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: snapshot.data!.length,
padding: const EdgeInsets.only(bottom: 8),
itemBuilder: (context, index) {
final spending = snapshot.data![index];
DateTime spendingDate = DateTime.parse(spending.date);
var initial =
DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
return spendingDate.isAfter(initial) && spendingDate.isBefore(end)
? Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
WeeklySpendingStream(
color: color,
snapshot: snapshot,
initialDate: initialDate,
endDate: endDate,
),
],
)
: const SizedBox();
},
);
}
WeeklySpendingStreamCode:
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: snapshot.data!.length,
padding: const EdgeInsets.only(bottom: 8),
itemBuilder: (context, index) {
final spending = snapshot.data![index];
DateTime spendingDate = DateTime.parse(spending.date);
var initial =
DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
if (spendingDate.isAfter(initial) && spendingDate.isBefore(end)) {
return SwipeActionCell(
editModeOffset: 0,
fullSwipeFactor: 0.50,
key: ObjectKey(snapshot.data![index]),
trailingActions: [
SwipeAction(
performsFirstActionWithFullSwipe: true,
color: Colors.transparent,
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.red,
),
child: getIconButton(Colors.red, IconlyBold.delete)),
onTap: (handler) async {
handler(true);
await Future.delayed(const Duration(milliseconds: 100));
snapshot.data!.removeAt(index);
SpendingDatabaseHelper.instance.removeMethod(spending.id!);
},
),
],
child: SpendingCard(
beneficiary: spending.beneficiary,
budgetSpent: currency.format(int.parse(spending.budgetSpent)),
date: DateFormat("dd-MM-yyyy")
.format(DateTime.parse(spending.date)),
colorValue: color,
),
);
} else {
return const SizedBox();
}
},
);
}
The output of snapshot.data:
[
{id: 8, budgetName: 🍣 Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-21},
{id: 7, budgetName: 🍣 Food & Beverage, beneficiary: dfgvsd, budgetSpent: 1, date: 2022-02-14},
{id: 4, budgetName: 🍣 Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-11},
{id: 10, budgetName: 🍣 Food & Beverage, beneficiary: ddd, budgetSpent: 1, date: 2022-02-11},
{id: 5, budgetName: 🍣 Food & Beverage, beneficiary: asxasd, budgetSpent: 1, date: 2022-02-06}
]
Would really appreciate any suggestions/ideas of how this can be resolved.
Thanks in advance!
Nevermind, I somehow found the solution by getting the data using ".where" and checking whether it is empty or not. It worked nicely.
var initial = DateTime(initialDate.year, initialDate.month, initialDate.day - 1);
var end = DateTime(endDate.year, endDate.month, endDate.day + 1);
final spending = snapshot.data!.where((s) =>
DateTime.parse(s.date).isAfter(initial) &&
DateTime.parse(s.date).isBefore(end));
return spending.isNotEmpty ||
DateTime.now().isAfter(initial) && DateTime.now().isBefore(end)
? spending.isEmpty
? Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
const Padding(
padding: EdgeInsets.only(top: 8, bottom: 16),
child: Text(
'No spending this week',
style: kCaption,
),
),
],
)
: ListView(
shrinkWrap: true,
controller: ScrollController(),
padding: const EdgeInsets.only(bottom: 8),
children: [
Column(
children: [
WeekDivider(label: label, dateEstimation: dateEstimation),
WeeklySpendingStream(
color: color,
snapshot: snapshot,
initialDate: initialDate,
endDate: endDate,
),
],
)
],
)
: const SizedBox();
}
I think your problem raised because of you just extract the database file without filtering so do this use .toSet() or assign the extracted data to a variable as a map or Set and use .toSet().

SetState does not rebuild ListView.builder

I am trying to create a comment page. The list of comments are listed using ListView.builder. And when the user enter a comment, it will rebuild the list again to include the newly added comment. But somehow the list is not rebuild and i'm getting this message in terminal:
Changing the content within the the composing region may cause the
input method to behave strangely, and is therefore discouraged. See
https://github.com/flut ter/flutter/issues/78827 for more details
The newly added comment only shows when i close the comment page and reopen it again. Please help me, as i am not sure what is the issue and how to fix it.
Comment Page:
import 'package:flutter/material.dart';
import '../model/model_comment.dart';
class CommentsPage extends StatefulWidget {
#override
_CommentsPageState createState() => _CommentsPageState();
}
class _CommentsPageState extends State<CommentsPage> {
ValueNotifier<int> _counter = ValueNotifier<int>(0);
TextEditingController _controllerComment = TextEditingController();
bool _hasComment = false;
#override
void dispose() {
_controllerComment.dispose();
super.dispose();
}
_commentOnSend() {
setState(() {
var value = CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/women/34.jpg",
name: "Laurent Oslo",
dateTime: "30 Dec 20 08:00",
comment: _controllerComment.text,
);
CommentModel.dummyData.insert(0, value);
});
_controllerComment.clear();
FocusScope.of(context).unfocus();
}
Widget _listView = ListView.builder(
itemCount: CommentModel.dummyData.length,
itemBuilder: (context, index) {
CommentModel _model = CommentModel.dummyData[index];
return Column(
children: <Widget>[
Divider(
height: 12.0,
),
Container(
padding: EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 3.0),
child: ListTile(
leading: CircleAvatar(
radius: 24.0,
backgroundImage: NetworkImage(_model.avatarUrl),
),
title: Text(_model.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_model.comment),
Text(_model.dateTime),
],
),
),
),
],
);
},
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Comments"),
titleSpacing: 40.0,
),
body: Container(
child: Column(
children: [
Expanded(child: _listView),
Divider(
height: 1.0,
),
ListTile(
leading: Container(
height: 40.0,
width: 40.0,
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.all(Radius.circular(50.0))),
child: CircleAvatar(
radius: 50.0,
backgroundImage: NetworkImage(
"https://randomuser.me/api/portraits/men/83.jpg")),
),
title: TextField(
decoration: (InputDecoration(
hintText: "Add Comment"
)),
minLines: 1,
maxLines: 5,
controller: _controllerComment,
onChanged: (val) {
setState(() {
_counter.value += 1;
if (val.isNotEmpty) {
_hasComment = true;
} else {
_hasComment = false;
}
});
}),
trailing: ValueListenableBuilder(
valueListenable: _counter,
builder: (BuildContext context, int value, Widget? child) {
return IconButton(
onPressed: _hasComment
? () {
_commentOnSend();
}
: null,
icon: Icon(Icons.send_sharp,
color: _hasComment ? Colors.deepPurple : null),
);
},
),
),
],
),
),
);
}
}
Comment Model Class:
class CommentModel {
final String avatarUrl;
final String name;
final String dateTime;
final String comment;
CommentModel(
{required this.avatarUrl,
required this.name,
required this.dateTime,
required this.comment});
static List<CommentModel> dummyData = [
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/women/34.jpg",
name: "Laurent Oslo",
dateTime: "30 Dec 20 08:00",
comment:
"There is a reason why I implemented it like this. In a comment section, the same comment widget can appear multiple times. So, the keys assigned to each widget needs to be different. Otherwise I won’t be able to refer to a specific widget later on",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/women/49.jpg",
name: "Tracy Wilbur",
dateTime: "01 Oct 20 17:00",
comment: "First Comment!",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/women/23.jpg",
name: "Michael Scott",
dateTime: "30 Sept 20 06:00",
comment:
"The idea is simple. Use the prefix with something else to make the key unique. In this case, I’ve used the index value to make them unique. I used the keys in line 25, 51, 56, and 60. See how I’ve done it in these lines.",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/45.jpg",
name: "Williams John",
dateTime: "17 Sept 20 02:00",
comment: "Join!",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/women/77.jpg",
name: "Claire Rach",
dateTime: "15 Aug 20 19:00",
comment:
"I want the comment section to be hidden away. A user can view comments by tapping to expand a widget. Meaning, the comment section should be collapsible. It will toggle between expanded and collapsed mode when being tapped.",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/81.jpg",
name: "Joe Panama",
dateTime: "05 Jul 20 03:00",
comment:
"A comment will have 3 data values which are commenting user details, time of comment posting and the actual text of the comment. I’ve created a “CommentModel” class to create this model.",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/83.jpg",
name: "Mark Hamill",
dateTime: "09 Jun 20 15:00",
comment:
"Because comments are part of a post, “PostModel” needs to have a list of comment data. So I’ve modified “PostModel” to have a list of “CommentModel” objects. Refer to the code changes to see what I’ve done.",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/85.jpg",
name: "Williams Dafoe",
dateTime: "25 May 20 20:00",
comment:
"Notice lines 18 to 29. I’ve used the “ExpansionTile” widget to create a collapsible list of comments. Each comment is a “_SingleComment” widget implemented in lines 34 to 67.",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/98.jpg",
name: "Phillips Mach",
dateTime: "01 Apr 20 17:00",
comment:
"New to app development and flutter in general(high schooler). Can I use this template? Do I have to give credit or can I just use it? At the very least, can I see the source code so I can learn from it?",
),
CommentModel(
avatarUrl: "https://randomuser.me/api/portraits/men/12.jpg",
name: "Joe Snowden",
dateTime: "04 Mar 20 16:00",
comment: "PM ME!",
),
];
}
This is because you are putting your Listview.builder in a state variable which is expected behavior since state variables do not get reinitialized in rebuilds.
If you want to refactor you can create a new function to return it:
ListView getList(){
return ListView.builder(
itemCount: CommentModel.dummyData.length,
itemBuilder: (context, index) {
CommentModel _model = CommentModel.dummyData[index];
return Column(
children: <Widget>[
Divider(
height: 12.0,
),
Container(
padding: EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 3.0),
child: ListTile(
leading: CircleAvatar(
radius: 24.0,
backgroundImage: NetworkImage(_model.avatarUrl),
),
title: Text(_model.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_model.comment),
Text(_model.dateTime),
],
),
),
),
],
);
},
);
}

How to create a/ instantiate a constructor from one file to the main dart file in flutter?

I am working on creating a hashMap for my flutter program and would like some input on it. As I created my hashMap in another dart file that is not the main dart file and I have no idea on how to connect it even when I created constrictors for the hashMaps. This is very important as the hashMap will be used on several files within the program hence why it is not in the main dart. Therefore I would like your guys input on how I could connect the two files.
This is part of my code:
main dart file:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
//Always use Stateless first then use stateful or stateless widgets afterward
#override
Widget build(BuildContext context) {
return MaterialApp(
//only used at the beginning of the program
title: 'The Cafe',
//just a title to the app it does not show for there is nothing telling it to show on the screen
debugShowCheckedModeBanner: false,
//takes out the ribbon at the top right corner of the screen and app
theme: ThemeData(
primarySwatch: Colors.green,
brightness: Brightness.dark,
fontFamily: 'georgia',
textTheme: TextTheme(headline1: TextStyle(fontSize: 100))
//controls the color of the very top part of the application
),
home: StartPage(),
//used to connect the Stateless widget to the Stateful widget below
);
}
}
class StartPage extends StatefulWidget {
#override
_StartPageState createState() => _StartPageState();
}
// do not forget the } prior to this comment if you do it will result in error and the program does not known why either
class _StartPageState extends State<StartPage> {
String value = "";
//stating the string is not seen until you have started to compute the drop-downs
//have the drop down's take you to the item page
//void main(){
//HashMap map = new HashMap<String, double>();
// LinkedHashMap linkedHashMap = new LinkedHashMap<int, String>();
// SplayTreeMap treeMap = new SplayTreeMap<int, String>();
//}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('The Campus Cafe'),
//where the main title is computed to be shown on the screen
centerTitle: true,
//centers the title
),
body: Center(
//This is Header that is after the main Title
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
//Header Container
Expanded(
child: Image.asset('assets/images/campus-cafe-logo-350sidebar.png',)
),
Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Text("Our Menu",style: TextStyle(fontSize: 30),
),
),
Expanded(
//Expands is used to create a body if you want a header and body...can also be used for other things but at the moment this is all I know
child: Column(
//there can be different types of Columns
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
//Padding is how far away one container or item is away from another as shown below
flex:3,
child: DropdownButton<String>(
//items are basically like an array or list
items: [
DropdownMenuItem<String>(
value: "1",
child: Center(
child: Text('Grilled Cheese'),
),
),
DropdownMenuItem<String>(
value: "2",
child: Center(
child: Text('Grilled Ham & Cheese'),
),
),
DropdownMenuItem<String>(
value: "3",
child: Center(
child: Text('BLT'),
),
),
DropdownMenuItem<String>(
value: "4",
child: Center(
child: Text('Western Chicken Sandwich'),
),
),
DropdownMenuItem<String>(
value: "5",
child: Center(
child: Text('Crispy Chicken Wrap'),
),
),
DropdownMenuItem<String>(
value: "6",
child: Center(
child: Text('Cheese Steak'),
),
),
],
onChanged: (_value) => {
print(_value.toString()),
setState(() {
value = _value;
}),
},
hint: Text('Sandwiches')
//This hint displays on your drop-box before you open it to see the items list
),
),
Flexible(
flex:3,
child: DropdownButton<String>(
items: [
DropdownMenuItem<String>(
value: "1",
child: Center(
child: Text('Quantum Burger'),
),
),
DropdownMenuItem<String>(
value: "2",
child: Center(
child: Text('Cheeseburger'),
),
),
DropdownMenuItem<String>(
value: "3",
child: Center(
child: Text('Double Cheeseburger 1/4'),
),
),
DropdownMenuItem<String>(
value: "4",
child: Center(
child: Text('Hamburger 1/4'),
),
),
DropdownMenuItem<String>(
value: "5",
child: Center(
child: Text('Cheeseburger'),
),
),
DropdownMenuItem<String>(
value: "6",
child: Center(
child: Text('Veggie Burger'),
),
),
],
onChanged: (_value) => {
print(_value.toString()),
setState(() {
value = _value;
}),
},
hint: Text('Burgers')),
),
This is my hashMap file:
List<String> sandwich = ["Veggie Melt", "Crispy Chicken Wrap", "Italian Meatball Sub",
"Chicken Parm Grinder", "Grill Cheese", "Grilled Ham & Cheese", "Bacon Bagel Melt"];
List<double> sandwichPrice = [4.50, 6.95, 6.99, 6.59, 3.59, 4.59, 5.29];
Map<String, double> map1 = Map.fromIterables(sandwich, sandwichPrice);
List<String> burgers = ["Veggie Burger", "The Quantum Burger", "Cafe Melt",
"The Bull Rider", "Double Cheese Burger", "Hamburger"];
List<double> burgerPrice = [4.99, 7.25, 6.59, 5.79, 5.89, 3.99, 3.79];
Map<String, double> map2 = Map.fromIterables(burgers, burgerPrice);
List<String> otherItems = ["Chicken Quesadilla", "Cheese Quesadilla",
"Chicken Strips", "Popcorn Chicken", "Jalapeno Poppers"];
List<double> otherItemsPrice = [6.79, 6.29, 4.99, 4.59, 3.49];
Map<String, double> map3 = Map.fromIterables(otherItems, otherItemsPrice);
List<String> sides = ["French Fries", "Onion Rings", "Jalapeno Cheese Curds",
"Tater Tots", "Pretzel Bites", "Nachos & Cheese"];
List<double> sidesPrice = [3.29, 4.79, 4.99, 3.19, 4.59, 3.50];
Map<String, double> map4 = Map.fromIterables(sides, sidesPrice);
List<String> pizza = ["7-inch Cheese", "7-inc with topping"];
List<double> pizzaPrice = [4.59, 4.99];
Map<String, double> map5 = Map.fromIterables(pizza, pizzaPrice);
class Menu {
String sandwich;
String burger;
String otherItems;
String sides;
String pizza;
double sandwichPrice;
double burgerPrice;
double otherItemsPrice;
double sidesPrice;
double pizzaPrice;
Menu.s(this.sandwich, this.sandwichPrice){}
Menu.b(this.burger, this.burgerPrice){}
Menu.o(this.otherItems, this.otherItemsPrice){}
Menu.q(this.sides, this.sidesPrice){}
Menu.p(this.pizza, this.pizzaPrice){}
}
First of all, you need to import your hashMap file. Then update your MyWidget as follows:
I have displayed here an example of how you can use the Dropdown with map1 (i.e, Sandwiches).
You have to just iterate over the keys of the hashmap 'map1' & create the list of DropdownMenuItem from it & pass this list to the items property.
For each dropdown, you will need to save the selected option, so instead of using String value = '';, I have changed it to String selectedSandwich = 'Sandwiches';. This part is crucial as the Dropdown widget can have its value only as one of the options available. So, if you do not have the Sandwiches option in your dropdown list, there will be an error. Hence, I have added the Sandwiches option in the initState of your MyAppWidget.
You have to do the same for the remaining of the hashmaps. Let me know if you need any more help.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Instead of value use selectedSandwich
String selectedSandwich = 'Sandwiches';
// List of sandwiches, you have to do the same for rest of the maps
// like: burgers, otherItems etc.
List<String> sandwiches = map1.keys.toList();
#override
void initState() {
super.initState();
// Adding sandwiches as an option is necessay as the dropdown's value
// must be equal to one of its options.
// I have done this only for sandwiches, but you need to do the same
// for rest.
sandwiches.insert(0, 'Sandwiches');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DropdownButton<String>(
items: sandwiches // using map1.keys.toList
.map(
(e) => DropdownMenuItem<String>(
value: e,
child: Center(
child: Text(e),
),
),
)
.toList(),
onChanged: (_value) => {
print(_value.toString()),
setState(() {
selectedSandwich = _value; // Set selected sandwich
}),
},
value: selectedSandwich, // Use value
),
],
),
);
}
}

How can I add new items to a selection text menu?

How can I add new items to the selection text popup menu (such as Copy/Paste/Cut/item2)
in a Flutter custom text selection menu
There is no easy way to do it, but there is a package that helps out with it. This article talks about the creation of the package. But if you use the package (which I would recommend) just wrap the widget you want to have this functionality with in a FocusedMenuHolder. The Readme contains the following example:
Expanded(
child: GridView(
physics: BouncingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
children: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
// Wrap each item (Card) with Focused Menu Holder
.map((e) => FocusedMenuHolder(
menuWidth: MediaQuery.of(context).size.width*0.50,
blurSize: 5.0,
menuItemExtent: 45,
menuBoxDecoration: BoxDecoration(color: Colors.grey,borderRadius: BorderRadius.all(Radius.circular(15.0))),
duration: Duration(milliseconds: 100),
animateMenuItems: true,
blurBackgroundColor: Colors.black54,
menuOffset: 10.0, // Offset value to show menuItem from the selected item
bottomOffsetHeight: 80.0, // Offset height to consider, for showing the menu item ( for example bottom navigation bar), so that the popup menu will be shown on top of selected item.
menuItems: <FocusedMenuItem>[
// Add Each FocusedMenuItem for Menu Options
FocusedMenuItem(title: Text("Open"),trailingIcon: Icon(Icons.open_in_new) ,onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context)=>ScreenTwo()));
}),
FocusedMenuItem(title: Text("Share"),trailingIcon: Icon(Icons.share) ,onPressed: (){}),
FocusedMenuItem(title: Text("Favorite"),trailingIcon: Icon(Icons.favorite_border) ,onPressed: (){}),
FocusedMenuItem(title: Text("Delete",style: TextStyle(color: Colors.redAccent),),trailingIcon: Icon(Icons.delete,color: Colors.redAccent,) ,onPressed: (){}),
],
onPressed: (){},
child: Card(
child: Column(
children: <Widget>[
Image.asset("assets/images/image_$e.jpg"),
],
),
),
))
.toList(),
),
),
You can use selectionControls parameter to customize text selection menu:
TextField(selectionControls: MyMaterialTextSelectionControls()),
and implement delegate class like here:
class MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
// Padding between the toolbar and the anchor.
static const double _kToolbarContentDistanceBelow = 10.0;
static const double _kToolbarContentDistance = 8.0;
/// Builder for material-style copy/paste text selection toolbar.
#override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
Offset? lastSecondaryTapDownPosition,
) {
final TextSelectionPoint startTextSelectionPoint = endpoints[0];
final TextSelectionPoint endTextSelectionPoint =
endpoints.length > 1 ? endpoints[1] : endpoints[0];
final Offset anchorAbove = Offset(
globalEditableRegion.left + selectionMidpoint.dx,
globalEditableRegion.top +
startTextSelectionPoint.point.dy -
textLineHeight -
_kToolbarContentDistance,
);
final Offset anchorBelow = Offset(
globalEditableRegion.left + selectionMidpoint.dx,
globalEditableRegion.top +
endTextSelectionPoint.point.dy +
_kToolbarContentDistanceBelow,
);
final value = delegate.textEditingValue;
return MyTextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
clipboardStatus: clipboardStatus,
handleCustomButton: () {
print(value.selection.textInside(value.text));
delegate.hideToolbar();
},
);
}
}
class MyTextSelectionToolbar extends StatelessWidget {
const MyTextSelectionToolbar({
Key? key,
required this.anchorAbove,
required this.anchorBelow,
required this.clipboardStatus,
required this.handleCustomButton,
}) : super(key: key);
final Offset anchorAbove;
final Offset anchorBelow;
final ClipboardStatusNotifier clipboardStatus;
final VoidCallback? handleCustomButton;
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final List<_TextSelectionToolbarItemData> items =
<_TextSelectionToolbarItemData>[
_TextSelectionToolbarItemData(
onPressed: handleCustomButton ?? () {},
label: 'Custom button',
),
];
int childIndex = 0;
return TextSelectionToolbar(
anchorAbove: anchorAbove,
anchorBelow: anchorBelow,
toolbarBuilder: (BuildContext context, Widget child) =>
Container(color: Colors.pink, child: child),
children: items
.map((_TextSelectionToolbarItemData itemData) =>
TextSelectionToolbarTextButton(
padding: TextSelectionToolbarTextButton.getPadding(
childIndex++, items.length),
onPressed: itemData.onPressed,
child: Text(itemData.label),
))
.toList(),
);
}
}
class _TextSelectionToolbarItemData {
const _TextSelectionToolbarItemData({
required this.label,
required this.onPressed,
});
final String label;
final VoidCallback onPressed;
}
Or checkout this one text_selection_controls

How to add a name to a chart on flutter, x- and y-axis?

I have been working with the online gallery of Flutter charts (https://google.github.io/charts/flutter/gallery.html) but I'm struggling to add a title for x & y axis values.
Can somebody help me or tell me how to add the labels to the graph?
Its possible using behaviors property, check the code
var chart = charts.LineChart(seriesList,
behaviors: [
new charts.ChartTitle('Dimension',
behaviorPosition: charts.BehaviorPosition.bottom,
titleStyleSpec: chartsCommon.TextStyleSpec(fontSize: 11),
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('Dose, mg',
behaviorPosition: charts.BehaviorPosition.start,
titleStyleSpec: chartsCommon.TextStyleSpec(fontSize: 11),
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea)
],
defaultRenderer: new charts.LineRendererConfig(includePoints: true));
Source https://google.github.io/charts/flutter/example/behaviors/chart_title
use the 'behavior' list for set title of chart
Widget build(BuildContext context) {
return new charts.LineChart(
seriesList,
animate: animate,
behaviors: [
new charts.ChartTitle('Top title text',
subTitle: 'Top sub-title text',
behaviorPosition: charts.BehaviorPosition.top,
titleOutsideJustification: charts.OutsideJustification.start,
innerPadding: 18),
new charts.ChartTitle('Bottom title text',
behaviorPosition: charts.BehaviorPosition.bottom,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('Start title',
behaviorPosition: charts.BehaviorPosition.start,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('End title',
behaviorPosition: charts.BehaviorPosition.end,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
],
);
}
You can do it by using behaviors using line annotations iterating your list data and make a new LineAnnotationSegment array but you should be aware that some titles may overlap when the next time point is very close.
final data = [
LinearPrices(DateTime(2020, 9, 19), 5),
LinearPrices(DateTime(2020, 9, 26), 15),
LinearPrices(DateTime(2020, 10, 3), 20),
LinearPrices(DateTime(2020, 10, 10), 17),
];
#override
Widget build(BuildContext context) {
return charts.TimeSeriesChart(seriesList, animate: false, behaviors: [
charts.RangeAnnotation( data.map((e) => charts.LineAnnotationSegment(
e.timestamp, charts.RangeAnnotationAxisType.domain,
middleLabel: '\$${e.price}')).toList()),
]);
}
Nevertheless you can use a callback to paint when the user clicks the line by painting either a custom text at the bottom or as a custom label using behaviors like this:
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:intl/intl.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final data = [
LinearPrices(DateTime(2020, 9, 19), 5),
LinearPrices(DateTime(2020, 9, 26), 15),
LinearPrices(DateTime(2020, 10, 3), 20),
LinearPrices(DateTime(2020, 10, 10), 17),
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Chart'),
),
body: ChartPricesItem(data),
));
}
}
class ChartPricesItem extends StatefulWidget {
final List<LinearPrices> data;
ChartPricesItem(this.data);
static List<charts.Series<LinearPrices, DateTime>> _createSeries(
List<LinearPrices> data) {
return [
charts.Series<LinearPrices, DateTime>(
id: 'Prices',
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault,
domainFn: (LinearPrices sales, _) => sales.timestamp,
measureFn: (LinearPrices sales, _) => sales.price,
data: data,
)
];
}
#override
_ChartPricesItemState createState() => _ChartPricesItemState();
}
class _ChartPricesItemState extends State<ChartPricesItem> {
DateTime _time;
double _price;
// Listens to the underlying selection changes, and updates the information relevant
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
DateTime time;
double price;
// We get the model that updated with a list of [SeriesDatum] which is
// simply a pair of series & datum.
if (selectedDatum.isNotEmpty) {
time = selectedDatum.first.datum.timestamp;
price = selectedDatum.first.datum.price;
}
// Request a build.
setState(() {
_time = time;
_price = price;
});
}
#override
Widget build(BuildContext context) {
final simpleCurrencyFormatter =
charts.BasicNumericTickFormatterSpec.fromNumberFormat(
NumberFormat.compactSimpleCurrency());
var behaviors;
// Check if the user click over the line.
if (_time != null && _price != null) {
behaviors = [
charts.RangeAnnotation([
charts.LineAnnotationSegment(
_time,
charts.RangeAnnotationAxisType.domain,
labelDirection: charts.AnnotationLabelDirection.horizontal,
labelPosition: charts.AnnotationLabelPosition.margin,
labelStyleSpec:
charts.TextStyleSpec(fontWeight: FontWeight.bold.toString()),
middleLabel: '\$$_price',
),
]),
];
}
var chart = charts.TimeSeriesChart(
ChartPricesItem._createSeries(widget.data),
animate: false,
// Include timeline points in line
defaultRenderer: charts.LineRendererConfig(includePoints: true),
selectionModels: [
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
changedListener: _onSelectionChanged,
)
],
// This is the part where you paint label when you click over the line.
behaviors: behaviors,
// Sets up a currency formatter for the measure axis.
primaryMeasureAxis: charts.NumericAxisSpec(
tickFormatterSpec: simpleCurrencyFormatter,
tickProviderSpec:
charts.BasicNumericTickProviderSpec(zeroBound: false)),
/// Customizes the date tick formatter. It will print the day of month
/// as the default format, but include the month and year if it
/// transitions to a new month.
///
/// minute, hour, day, month, and year are all provided by default and
/// you can override them following this pattern.
domainAxis: charts.DateTimeAxisSpec(
tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
day: charts.TimeFormatterSpec(
format: 'd', transitionFormat: 'dd/MM/yyyy'),
minute: charts.TimeFormatterSpec(
format: 'mm', transitionFormat: 'dd/MM/yyyy HH:mm'))),
);
var chartWidget = Padding(
padding: EdgeInsets.all(16),
child: SizedBox(
height: 200.0,
child: chart,
),
);
final children = <Widget>[chartWidget];
// If there is a selection, then include the details.
if (_time != null) {
children.add(Padding(
padding: EdgeInsets.only(top: 4.0),
child: Text(DateFormat('dd/MM/yyyy hh:mm').format(_time),
style: Theme.of(context).textTheme.bodyText1)));
}
return SingleChildScrollView(
child: Column(
children: <Widget>[
const SizedBox(height: 8),
Text("Product Prices", style: Theme.of(context).textTheme.headline5),
Column(children: children),
],
),
);
}
}
/// Sample linear data type.
class LinearPrices {
final DateTime timestamp;
final double price;
LinearPrices(this.timestamp, this.price);
}
This is the result: