Displaying Json array in expansion tile card - expansion-tile

I want to display individual project data from a Json array in the expansion tile card and then display a list of cards based on the number of projects available. I am able to fetch the data but the card doesn't display or even show on the screen; it is blank and there is no error.
Below is the code for displaying the expansion tile card and making a list:
``` import 'dart:convert';
import 'package:expansion_tile_card/expansion_tile_card.dart';
import 'package:flutter/material.dart';
import 'package:mesys/network_utils/api.dart';
import 'package:mesys/models/dummy_model.dart';
class ProjectWidget extends StatefulWidget {
const ProjectWidget({Key? key}) : super(key: key);
#override
_ProjectWidgetState createState() => _ProjectWidgetState();
}
class _ProjectWidgetState extends State<ProjectWidget> {
GlobalKey<ExpansionTileCardState> cardA = GlobalKey();
final List<ProjectModel> _projects = <ProjectModel>[];
Future<List<ProjectModel>> _fetchProjects() async {
var res = await Network().getData('users/project');
var projects = <ProjectModel>[];
if (res.statusCode == 200) {
var body = json.decode(res.body);
var tdata = body['data'];
var projectsJson = tdata;
for (var projectJson in projectsJson) {
projects.add(ProjectModel.fromJson(projectJson));
}
}
return projects;
}
#override
Widget build(BuildContext context) {
_fetchProjects().then((value) {
_projects.addAll(value);
});
return Container(
child: ListView.builder(
itemCount: _projects.length,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20.0, vertical: 20),
child: ExpansionTileCard(
baseColor: const Color.fromRGBO(0, 161, 39, 1),
expandedColor: Colors.amber,
key: cardA,
leading: CircleAvatar(
foregroundImage:
Image.asset('assets/images/progress.png')
.image),
title: Text(_projects[index].title,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold)),
subtitle: Text(_projects[index].location,
style: const TextStyle(color: Colors.black)),
children: <Widget>[
const Divider(
thickness: 1,
height: 1,
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Text(_projects[index].description),
)),
])),
);
}));
}
} ```
Please help me solve why it isn't displaying, thanks.

Related

Flutter 'setState' of other Widget

i want to code a POS for german 'Fischbrötchen'. My problem is that the "View" of the Ordertabel dosn't update. I tried man things but nothing worked... can someone help me to point out my Problem ?
So when i click a button a Order should add to the Orders List and then update the View to display the order.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
home: MyHomePage(),
debugShowCheckedModeBanner: false,
theme: CupertinoThemeData(
brightness: Brightness.light, primaryColor: Colors.black54),
);
}
}
ValueNotifier<int> KundenId = ValueNotifier<int>(0);
List<Map<String, dynamic>> orders = [];
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
final List Getraenke = ["Fritz", "Wasser", "Bier"];
List<Map<String, dynamic>> items = [
{'name': 'Möltenorter', 'price': '4 Euro'},
{'name': 'Matjes', 'price': '4 Euro'},
{'name': 'Bismarkt', 'price': '4 Euro'},
{'name': 'Krabben', 'price': '5,50 Euro'},
{'name': 'Lachs', 'price': '5.50 Euro'},
{'name': 'Lachs Kalt', 'price': '5.50 Euro'},
];
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: RightSideContainer(),
);
}
}
class RightSideContainer extends StatefulWidget {
#override
State<StatefulWidget> createState() => RightSideContainerState();
}
class RightSideContainerState extends State<RightSideContainer> {
#override
Widget build(BuildContext context) {
return Row(
children: [
//left side, eingabe
Column(
children: [
Text("Kasse"),
Container(
height: 600,
width: MediaQuery.of(context).size.width / 2,
child: Padding(
padding: EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black54,
),
alignment: AlignmentDirectional.topStart,
child: OrderTable(),
))),
],
),
//right side, Ausgabe
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(0),
color: Colors.black.withOpacity(0.5),
),
width: MediaQuery.of(context).size.width / 2,
alignment: Alignment.centerRight,
child: Column(
children: [
Container(
height: 500,
color: Colors.red,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return ButtonPrefab(items_: items[index]);
}),
),
],
))
],
);
}
}
class ButtonPrefab extends StatelessWidget {
final Map<String, dynamic> items_;
const ButtonPrefab({required this.items_});
void addOrder(name, price) {
orders.add({
'kundenId': 0,
'bestellung': name,
'price': price,
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
child: Text(items_['name']),
color: Colors.black54,
padding: EdgeInsets.all(3),
onPressed: () {
print(orders);
addOrder("name", 2.4);
KundenId.value++;
print(KundenId.value);
},
),
);
}
}
class OrderTable extends StatefulWidget {
#override
State<OrderTable> createState() => _OrderTableState();
}
class _OrderTableState extends State<OrderTable> {
#override
void initState() {
super.initState();
setState(() {});
}
void update() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return DataTable(
columnSpacing: 20,
columns: [
DataColumn(
label: Text(
'Kunden ID',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
DataColumn(
label: Text(
'Bestellung',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
DataColumn(
label: Text(
'Preis',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
],
rows: orders
.map(
(order) => DataRow(
cells: [
DataCell(
Text(
order['kundenId'].toString(),
style: TextStyle(fontSize: 16),
),
),
DataCell(
Text(
order['bestellung'],
style: TextStyle(fontSize: 16),
),
),
DataCell(
Text(
order['price'].toString(),
style: TextStyle(fontSize: 16),
),
),
],
),
)
.toList(),
);
})
],
),
);
}
}
I tried to use 'set State' in my Statefull Widget but is dosn't change anything..
Deleted my previous answer and tested your code... and got it working now.
I see you have a Function named update() and you're even using it there, but should use it somewhere else as a callback Function. A callback Function helps you to edit values in your "previous" Widget that called this Widget. Read more here:
How to pass callback in Flutter
Also you have setState() in initState. Don't see the reason to have this there either. You should use setState in initState only for some kind of asyncronus reason, as explained here: Importance of Calling SetState inside initState
Call setState in "previous" Widget on button press after adding your item by using a callback Function (for short keeping, here is only the modified code):
class RightSideContainerState extends State<RightSideContainer> {
void update() { //this is a new Function
setState(() {});
}
#override
Widget build(BuildContext context) {
return Row(
children: [
//left side, eingabe
Column(
children: [
Text("Kasse"),
Container(
height: 600,
width: MediaQuery.of(context).size.width / 2,
child: Padding(
padding: EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black54,
),
alignment: AlignmentDirectional.topStart,
child: OrderTable(),
))),
],
),
//right side, Ausgabe
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(0),
color: Colors.black.withOpacity(0.5),
),
width: MediaQuery.of(context).size.width / 2,
alignment: Alignment.centerRight,
child: Column(
children: [
Container(
height: 500,
color: Colors.red,
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return ButtonPrefab(items_: items[index], callbackFunction: update); //give the "update (setState)" Function to the "next" Widget for calling it later
}),
),
],
))
],
);
}
}
class ButtonPrefab extends StatelessWidget {
final Map<String, dynamic> items_;
final Function callbackFunction; //get the callback Function of the calling Widget
const ButtonPrefab({required this.items_, required this.callbackFunction}); //get the callback Function of the calling Widget
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
child: Text(items_['name']),
color: Colors.black54,
padding: EdgeInsets.all(3),
onPressed: () {
print(orders);
// addOrder("name", 2.4); // you are always giving "name" and 2.4, but probably need to give the item that's being pushed
addOrder(items_['name'], items_['price']); //like this
KundenId.value++;
print(KundenId.value);
callbackFunction(); //this is the "update" Function I created in the calling Widget, but in this Widget it has a name "callbackFunction"
},
),
);
}
}
class _OrderTableState extends State<OrderTable> {
#override
void initState() {
super.initState();
// setState(() {}); // not necessary
}
// void update() { // not necessary
// setState(() {});
// }
}
There is so many problems here, that I cannot list them one by one.
The basic underlying problem here is that you think having a global variable is a good method to keep your state. It is not. Never has been. In no programming language in the last quarter of a century.
To hold your state (in your case I guess it's orders) use one of the state management patterns.
I suggest taking a look at Provider first. Not because it's the best, but because it is the easiest and explains your problem clearly:
Simple app state management
Once your applications get larger, my personal preference is BLoC, but that is a little to complex for this problem.

Error: The operator '[]' isn't defined for the class 'Object?'. - 'Object' is from 'dart:core'

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../db/category.dart';
import '../db/brand.dart';
import 'admin.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
class AddProduct extends StatefulWidget {
const AddProduct({Key? key}) : super(key: key);
#override
State<AddProduct> createState() => _AddProductState();
}
class _AddProductState extends State<AddProduct> {
final CategoryService _categoryService = CategoryService();
final BrandService _brandService = BrandService();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
TextEditingController productNameController = TextEditingController();
List<DocumentSnapshot> brands = <DocumentSnapshot>[];
List<DocumentSnapshot> categories = <DocumentSnapshot>[];
List<DropdownMenuItem<String>>? categoriesDropDown =
<DropdownMenuItem<String>>[];
List<DropdownMenuItem<String>> brandsDropDown = <DropdownMenuItem<String>>[];
late String _currentCategory = "";
late String _currentBrand ="";
Color white = Colors.white;
Color black = Colors.black;
Color grey = Colors.grey;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
elevation: 0.3,
backgroundColor: white,
leading: IconButton(
color: black,
icon: const Icon(Icons.close),
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => const Admin()));
},
),
title: Text(
"Ürün ekle",
style: TextStyle(color: black),
),
),
body: Form(
key: _formKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: () {},
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 40, 14, 40),
child: Icon(
Icons.add,
color: grey,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: () {},
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 40, 14, 40),
child: Icon(
Icons.add,
color: grey,
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: () {},
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 40, 14, 40),
child: Icon(
Icons.add,
color: grey,
),
),
),
),
),
],
),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Ürün ismi 5 karakterden uzun olmalıdır!",
style: TextStyle(
fontSize: 16,
color: Colors.red,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextFormField(
controller: productNameController,
decoration: const InputDecoration(
hintText: "Ürün İsmi",
),
validator: (value) {
if (value!.isEmpty) {
return "Ürün ismi girmelisin";
} else if (value.length > 5) {
return "Ürün ismi 5 harften uzun olmalıdır";
}
return null;
},
),
),
//selected category
Padding(
padding: const EdgeInsets.all(8.0),
child: TypeAheadField(
textFieldConfiguration: const TextFieldConfiguration(
autofocus: false,
decoration: InputDecoration(hintText: "Kategori ekle" )),
suggestionsCallback: (pattern) async {
return await _categoryService.getSuggestions(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
leading: const Icon(Icons.category),
title: Text(suggestion['category']),
);
},
onSuggestionSelected: (suggestion) {
_currentCategory=suggestion["category"];
},
),
),
//selected brand
Padding(
padding: const EdgeInsets.all(8.0),
child: TypeAheadField(
textFieldConfiguration: const TextFieldConfiguration(
autofocus: false,
decoration: InputDecoration(hintText: "Marka ekle")),
suggestionsCallback: (pattern) async {
return await _brandService.getSuggestions(pattern);
},
itemBuilder: (context, suggestion) {
return ListTile(
leading: const Icon(Icons.category),
title: Text(suggestion["brand"]),
);
},
onSuggestionSelected: (suggestion) {
setState(() {
_currentBrand = "$suggestion";
});
},
),
),
],
),
),
);
}
#override
void initState() {
_getCategories();
//_getBrands();
getCategoriesDropdown();
// _currentCategory = categoriesDropDown![0].value!;
super.initState();
}
List<DropdownMenuItem<String>> ?getCategoriesDropdown() {
List<DropdownMenuItem<String>> items = [];
for (int i = 0; i < categories.length; i++) {
setState(() {
categoriesDropDown?.insert(
0,
DropdownMenuItem(
child: Text(categories[i]["category"]),
value: categories[i]["category"],
));
});
}
return items;
}
void _getCategories() async {
List<DocumentSnapshot> data = await _categoryService.getCategories();
setState(() {
categories = data;
});
}
}
Performing hot reload...
Syncing files to device sdk gphone x86...
lib/screens/add_product.dart:150:42: Error: The operator '[]' isn't defined for the class 'Object?'.
'Object' is from 'dart:core'.
Try correcting the operator to an existing operator, or defining a '[]' operator.
title: Text(suggestion['category']),
^
lib/screens/add_product.dart:154:45: Error: The operator '[]' isn't defined for the class 'Object?'.
'Object' is from 'dart:core'.
Try correcting the operator to an existing operator, or defining a '[]' operator.
_currentCategory=suggestion["category"];
^
lib/screens/add_product.dart:172:43: Error: The operator '[]' isn't defined for the class 'Object?'.
'Object' is from 'dart:core'.
Try correcting the operator to an existing operator, or defining a '[]' operator.
title: Text(suggestion["brand"]),
^
edit__________________________________________________________________________________________________________________________________________
import 'package:uuid/uuid.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class CategoryService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
String ref = "categories";
void createCategory(String name) {
var id = const Uuid();
String categoryId = id.v1();
_firestore.collection(ref).doc(categoryId).set({"category": name});
}
Future<List<DocumentSnapshot>> getCategories() {
return _firestore.collection(ref).get().then((snaps) {
return snaps.docs;
});
}
Future<List<DocumentSnapshot>> getSuggestions(String suggestion) =>
_firestore.collection(ref).where("category", isEqualTo: suggestion).get().then((snap){
return snap.docs;
});
}
import 'package:uuid/uuid.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:convert';
class BrandService{
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
String ref= "brands";
void createBrand(String name){
var id = const Uuid();
String brandId = id.v1();
_firestore.collection(ref).doc(brandId).set({"brand": name});
}
Future<List<DocumentSnapshot>> getBrands()=>
_firestore.collection(ref).get().then((snaps){
return snaps.docs;
});
Future<List<DocumentSnapshot>> getSuggestions(String suggestion) =>
_firestore.collection(ref).where("brand", isEqualTo: suggestion).get().then((snap) {
return snap.docs;
});
}
Your categories variable is a List<DocumentSnapshot>. A DocumentSnapshot is a generic type, and if you don't specify what data it contains that data will be of type Object. And you can't call [] on an object, which is why you get the error message when you do this:
categories[i]["category"]
The solution is to either pass a type to the DocumentSnapshot, or to use the get() method, or to cast the data to a Map.
Using the get method:
categories[i].get("category")
Casting to a map:
(categories[i].data() as Map<String,dynamic>)["category"]
Also see:
Firebase Firestore Error: The operator '[]' isn't defined for the class 'Object'
The reference documentation for DocumentSnapshot

Don't center a PageView - Flutter

I am currently using the carousel-slider library to get a carousel in Flutter.
This library is based on a PageView, and in a PageView the elements are centered.
That's the carousel I get:
And this is what I'd like to have:
Here is the code where is use the CarouselSlider:
CarouselSlider(
height: 150,
viewportFraction: 0.5,
initialPage: 0,
enableInfiniteScroll: false,
items: widget.user.lastGamesPlayed.map((game) {
return Builder(
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: GestureDetector(
onTap: () {
game.presentGame(context, widget.user);
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(game.name,
style: TextStyle(fontSize: 70),
maxLines: 1)),
),
))));
},
);
}).toList(),
)
And here is the code inside the CarouselSlider library:
#override
Widget build(BuildContext context) {
return getWrapper(PageView.builder(
physics: widget.isScrollEnabled
? AlwaysScrollableScrollPhysics()
: NeverScrollableScrollPhysics(),
scrollDirection: widget.scrollDirection,
controller: widget.pageController,
reverse: widget.reverse,
itemCount: widget.enableInfiniteScroll ? null : widget.items.length,
onPageChanged: (int index) {
int currentPage =
_getRealIndex(index, widget.realPage, widget.items.length);
if (widget.onPageChanged != null) {
widget.onPageChanged(currentPage);
}
},
itemBuilder: (BuildContext context, int i) {
final int index = _getRealIndex(
i + widget.initialPage, widget.realPage, widget.items.length);
return AnimatedBuilder(
animation: widget.pageController,
child: widget.items[index],
builder: (BuildContext context, child) {
// on the first render, the pageController.page is null,
// this is a dirty hack
if (widget.pageController.position.minScrollExtent == null ||
widget.pageController.position.maxScrollExtent == null) {
Future.delayed(Duration(microseconds: 1), () {
setState(() {});
});
return Container();
}
double value = widget.pageController.page - i;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
final double height = widget.height ??
MediaQuery.of(context).size.width * (1 / widget.aspectRatio);
final double distortionValue = widget.enlargeCenterPage
? Curves.easeOut.transform(value)
: 1.0;
if (widget.scrollDirection == Axis.horizontal) {
return Center(
child:
SizedBox(height: distortionValue * height, child: child));
} else {
return Center(
child: SizedBox(
width:
distortionValue * MediaQuery.of(context).size.width,
child: child));
}
},
);
},
));
}
How can I prevent elements from being centered?
Thank you in advance
If you don't want to animate page size over scroll there is no need to use this carousel-slider library.
Also, PageView is not the best Widget to achieve the layout you want, you should use a horizontal ListView with PageScrollPhysics.
import 'package:flutter/material.dart';
class Carousel extends StatelessWidget {
Carousel({
Key key,
#required this.items,
#required this.builderFunction,
#required this.height,
this.dividerIndent = 10,
}) : super(key: key);
final List<dynamic> items;
final double dividerIndent;
final Function(BuildContext context, dynamic item) builderFunction;
final double height;
#override
Widget build(BuildContext context) {
return Container(
height: height,
child: ListView.separated(
physics: PageScrollPhysics(),
separatorBuilder: (context, index) => Divider(
indent: dividerIndent,
),
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (context, index) {
Widget item = builderFunction(context, items[index]);
if (index == 0) {
return Padding(
child: item,
padding: EdgeInsets.only(left: dividerIndent),
);
} else if (index == items.length - 1) {
return Padding(
child: item,
padding: EdgeInsets.only(right: dividerIndent),
);
}
return item;
}),
);
}
}
Usage
Carousel(
height: 150,
items: widget.user.lastGamesPlayed,
builderFunction: (context, item) {
return ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(25)),
child: Container(
width: 200,
color: Theme.MyColors.lightBlue,
child: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: AutoSizeText(
item.name,
style: TextStyle(fontSize: 70),
maxLines: 1,
),
),
),
),
);
},
)
UPDATE
As observed by #AdamK, my solution doesn't have the same scroll physics behavior as a PageView, it acts more like a horizontal ListView.
If you are looking for this pagination behavior you should consider to write a custom ScrollPhysics and use it on your scrollable widget.
This is a very well explained article that helps us to achieve the desired effect.

How to test Cupertino Picker in Flutter

I'm writing widget testing the Cupertino Picker for the different values chosen by the use. I can't find any good tutorial. I followed this https://github.com/flutter/flutter/blob/master/packages/flutter/test/cupertino/picker_test.dart but this won't work for my case. In my case when the user chooses the value from the picker the test case should check whether the user chooses the correct value or default value.
Cupertino Picker code :
List<String> ages1 = ["-- select --"];
List<String> ages2 = List<String>.generate(
45, (int index) => (21 + index).toString(),
growable: false);
List<String> ages = [ages1, ages2].expand((f) => f).toList();
picker.dart:
Widget _buildAgePicker(BuildContext context) {
final FixedExtentScrollController scrollController =
FixedExtentScrollController(initialItem: _selectedAgeIndex);
return GestureDetector(
key: Key("Age Picker"),
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker(
CupertinoPicker(
key: Key("Age picker"),
scrollController: scrollController,
itemExtent: dropDownPickerItemHeight,
backgroundColor: Theme.of(context).canvasColor,
onSelectedItemChanged: (int index) {
setState(() {
_selectedAgeIndex = index;
ageValue = ages[index];
if (ageValue == S.of(context).pickerDefaultValue) {
ageDividerColor = Theme.of(context).errorColor;
errorText = S.of(context).pickerErrorMessage;
ageDividerWidth = 1.2;
} else {
ageDividerColor = Colors.black87;
errorText = "";
ageDividerWidth = 0.4;
}
});
},
children: List<Widget>.generate(ages.length, (int index) {
return Center(
child: Text(ages[index]),
);
}),
),
);
},
);
},
child: _buildMenu(
<Widget>[
Text(
S.of(context).Age,
style: TextStyle(fontSize: 17.0),
),
Text(
ages[_selectedAgeIndex],
),
],
),
);
}
Widget _buildMenu(List<Widget> children) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
height: 44.0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SafeArea(
top: false,
bottom: false,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
),
),
);
}
Widget _buildBottomPicker(Widget picker) {
return Container(
height: dropDownPickerSheetHeight,
padding: const EdgeInsets.only(top: 6.0),
color: Theme.of(context).canvasColor,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 22.0,
),
child: GestureDetector(
key: Key("picker"),
onTap: () {},
child: SafeArea(
top: false,
child: picker,
),
),
),
);
}
test code :
testWidgets("picker test",(WidgetTester tester)async{
await tester.tap(find.byKey(Key("Age Picker")));
await tester.drag(find.byKey(Key("Age Picker")), Offset(0.0,70.0));
await tester.pumpAndSettle();
expect(ages[1], "21");
});
I used a similar example in my golden test. I modified it a little in order to fit your case. However, the most important part is calling both methods inclusive: fling and drag. If you call only one of them it won't work. At least in my case, that was what happened.
testWidgets("cupertino picker test", (WidgetTester tester) async{
// Find the gesture detector that invoke the cupertino picker
final gestureDetectorFinder = find.byKey(Key('Age Picker'));
await tester.tap(gestureDetectorFinder);
await tester.pump();
// Find the default option (the first one)
final ageFinder = find.text('21').last;
expect(ageFinder, findsOneWidget);
// Apply an offset to scroll
const offset = Offset(0, -10000);
// Use both methods: fling and drag
await tester.fling(
ageFinder,
offset,
1000,
warnIfMissed: false,
);
await tester.drag(
ageFinder,
offset,
warnIfMissed: false,
);
});
What errors did you receive? I've tried creating a minimal repro from the CupertinoPicker snippet you've provided and I did encounter some issues in testWidgets().
Some of the issues that I've noticed is that CupertinoPicker has "Age picker" as its key and GestureDetector has "Age Picker" key set. Note that the key is case-sensitive. Since you're going to test CupertinoPicker, the key set on GestureDetector seems to be unnecessary.
Aside from that, no widget was built for the test. I suggest going through the official docs for Flutter testing to get started https://flutter.dev/docs/cookbook/testing/widget/introduction
Here's the repro I've created from the snippets you've provided.
Code for testing widgets
void main(){
var ages = [18, 19, 20, 21, 22, 24, 24, 25];
testWidgets("CupertinoPicker test", (WidgetTester tester) async {
// build the app for the test
// https://flutter.dev/docs/cookbook/testing/widget/introduction#4-build-the-widget-using-the-widgettester
await tester.pumpWidget(MyApp());
// key should match the key set in the widget
await tester.tap(find.byKey(Key("Age Picker")));
await tester.drag(find.byKey(Key("Age Picker")), Offset(0.0, 70.0));
await tester.pumpAndSettle();
expect(ages[3], 21);
});
}
Sample code for the app
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child:
testPicker(),
),
);
}
var _selectedAgeIndex = 0;
var scrollController = FixedExtentScrollController();
var dropDownPickerItemHeight = 50.0;
var ageValue;
var ages = [18, 19, 20, 21, 22, 24, 24, 25];
testPicker(){
return CupertinoPicker(
key: Key("Age Picker"),
scrollController: scrollController,
itemExtent: dropDownPickerItemHeight,
backgroundColor: Theme.of(context).canvasColor,
onSelectedItemChanged: (int index) {
setState(() {
_selectedAgeIndex = index;
ageValue = ages[index];
print('CupertinoPicker age[$index]: ${ages[index]}');
// if (ageValue == S.of(context).pickerDefaultValue) {
// ageDividerColor = Theme.of(context).errorColor;
// errorText = S.of(context).pickerErrorMessage;
// ageDividerWidth = 1.2;
// } else {
// ageDividerColor = Colors.black87;
// errorText = "";
// ageDividerWidth = 0.4;
// }
});
},
children: List<Widget>.generate(ages.length, (int index) {
return Center(
child: Text('${ages[index]}'),
);
}),
);
}
}

How to use a dismissible widget inside a CustomScrollView in Flutter?

I am trying to create a list of dismissible cards inside a customscrollview. The cards are getting rendered, but when i swipe the cards to dismiss them , they don't get removed from the list. Below is the code. Please help.
CustomScrollView customScroll = new CustomScrollView(
slivers: <Widget>[
new SliverAppBar(
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
expandedHeight: 90.0,
title: new Text("Test"),
),
new SliverFixedExtentList(
itemExtent: 128.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Dismissible(key: new ObjectKey(objects[index]),
child: widget.widgetAdapter(objects[index]),
onDismissed: (DismissDirection direction) {
setState(() {
this.objects.removeAt(index);
this.reIndex();
});
direction == DismissDirection.endToStart ? print(
"favourite") : print("remove");
},
background: new Container(
color: const Color.fromRGBO(183, 28, 28, 0.8),
child: const ListTile(
leading: const Icon(
Icons.delete, color: Colors.white, size: 36.0)
)
),
secondaryBackground: new Container(
color: const Color.fromRGBO(0, 96, 100, 0.8),
child: const ListTile(
trailing: const Icon(
Icons.favorite, color: Colors.white, size: 36.0)
)
),
);
},
childCount: objects.length,
),
),
]
);
your attempt is basically correct - I have simplified list creation and replaced it in your sample code below - what you are looking for is in the dmiss function # line 35;
import 'package:flutter/material.dart';
class TestDismissCSV extends StatefulWidget {
#override
_TestDismissCSVState createState() => new _TestDismissCSVState();
}
class _TestDismissCSVState extends State<TestDismissCSV> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Dismiss in Cust Scroll V",
theme: new ThemeData(brightness: Brightness.dark),
home: new Scaffold(
body: dmiss(context),
),
);
}
List<TheListClass> _theList;
Widget dmiss(context) {
return new CustomScrollView(slivers: <Widget>[
new SliverAppBar(
backgroundColor: Colors.black,
automaticallyImplyLeading: false,
expandedHeight: 90.0,
title: new Text("Test"),
),
new SliverFixedExtentList(
itemExtent: 128.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Dismissible(
key: new ObjectKey(_theList[index]),
child: new Material(child: new Text(_theList[index].title)),
onDismissed: (DismissDirection direction) {
setState(() {
this._theList.removeAt(index);
//this.reIndex();
});
direction == DismissDirection.endToStart
? print("favourite")
: print("remove");
},
background: new Container(
color: const Color.fromRGBO(183, 28, 28, 0.8),
child: const ListTile(
leading: const Icon(Icons.delete,
color: Colors.white, size: 36.0))),
secondaryBackground: new Container(
color: const Color.fromRGBO(0, 96, 100, 0.8),
child: const ListTile(
trailing: const Icon(Icons.favorite,
color: Colors.white, size: 36.0))),
);
},
childCount: _theList.length,
),
),
]);
}
#override
void initState() {
super.initState();
_theList = new List<TheListClass>();
for (var i = 0; i < 100; i++) {
_theList.add(new TheListClass('List Item ' + i.toString()));
}
}
#override
void dispose() {
super.dispose();
}
}
class TheListClass {
String title;
TheListClass(this.title);
}
List Item dismissed
Happy coding!

Resources