How do I create a function that changes data on page so I don't have to create multiple different pages for each stall - flutter

I have to create multiple files for different stalls but it seems so wrong and I know there's a better way but I just don't know how. Is there a way to create something like a page builder that will let me create multiple pages with different information from a single file. The difficult part is to make the onTap function of the images send the user to the stall_page of the selected stall. I tried doing this by making a view attribute in which I create a page and manually import the page route. But that involves creating a stall_info and stall_page for every single stall.
Instead of creating stall1_page, stall2_page and so on, can I create a generic stall function that will use the same page but just change the data? I know that's LITERALLY the point of object oriented programming languages but I'm really new to them as you'll tell my previous stupid questions.
This is the homescreen dashboard
class GridDashboard extends StatelessWidget {
Item item1 = Item(
title: 'Tray blazers',
subtitle: 'Open',
event: 'by Chef Tracy',
img: 'assets/images/tray_blazers-cr.png',
view: stallPage,
);
Item item2 = Item(
title: 'Papa Rimz',
subtitle: 'Open',
event: '',
img: 'assets/images/papa_rimz.png',
view: papaRimzPage,
);
Item item3 = Item(
title: 'W SAUCE',
subtitle: 'Open',
event: '',
img: 'assets/images/w_sauce-removebg.png',
view: wSaucePage,
);
Item item4 = Item(
title: 'African Kitchen',
subtitle: 'Open',
event: '',
img: 'assets/images/cherry-kitchen.png',
view: africanKitchenPage,
);
Item item5 = Item(
title: 'Suya Craze',
subtitle: 'Open',
event: '',
img: 'assets/images/suya_craze.png',
view: suyaCrazePage,
);
Item item6 = Item(
title: 'Zulkys cafe',
subtitle: 'Open',
event: '',
img: 'assets/images/zulkys-removeb.png',
view: zulkysCafePage,
);
Item item7 = Item(
title: 'Street food',
subtitle: 'Open',
event: '',
img: 'assets/images/street_food--removebg-.png',
view: streetFoodPage,
);
#override
Widget build(BuildContext context) {
List<Item> myList = [
item1,
item2,
item3,
item4,
item5,
item6,
item7,
];
return Flexible(
child: GridView.count(
childAspectRatio: 1.0,
padding: const EdgeInsets.only(left: 16, right: 16),
crossAxisCount: 2,
crossAxisSpacing: 18,
mainAxisSpacing: 18,
children: myList.map(
(data) {
return Container(
decoration: BoxDecoration(
color: const Color(0xff453658),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(data.view);
},
child: Image.asset(
data.img,
width: 90, //double.infinity
),
),
const SizedBox(height: 14),
Text(
data.title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
data.subtitle,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 10,
color: Colors.white38,
),
),
const SizedBox(height: 8),
// Text(
// data.event,
// style: const TextStyle(
// fontWeight: FontWeight.w600,
// fontSize: 11,
// color: Colors.white70,
// ),
// ),
],
),
);
},
).toList(),
),
);
}
}
class Item {
String title;
String subtitle;
String event;
String img;
String view;
Item({
required this.title,
required this.subtitle,
required this.event,
required this.img,
required this.view,
});
}
This is my stall_page:
class StallPage extends StatefulWidget {
const StallPage({super.key});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xff392850), //kBackground,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomAppBar(
Icons.arrow_back_ios_outlined,
Icons.search_outlined,
leftCallback: () => Navigator.of(context).pop(),
),
StallInfo(), //
FoodList(
selected,
(int index) {
setState(() {
selected = index;
});
pageController.jumpToPage(index);
},
stall,
),
Expanded(
child: FoodListView(
selected,
(int index) {
setState(() {
selected = index;
});
},
pageController,
stall,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 25),
height: 60,
child: SmoothPageIndicator(
controller: pageController,
count: stall.menu.length,
effect: CustomizableEffect(
dotDecoration: DotDecoration(
width: 8,
height: 8,
color: Colors.grey.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
),
activeDotDecoration: DotDecoration(
width: 10,
height: 10,
color: kBackground,
borderRadius: BorderRadius.circular(10),
dotBorder: const DotBorder(
color: kPrimaryColor,
padding: 2,
width: 2,
),
),
),
onDotClicked: (index) => pageController.jumpToPage(index),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: kPrimaryColor,
elevation: 2,
child: const Icon(
Icons.shopping_cart_outlined,
color: Colors.black,
size: 30,
),
),
);
}
}
This is my stall_info
class StallInfo extends StatelessWidget {
final stall = Stall.generateRestaurant1();
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stall.name,
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Row(
children: [
Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.blueGrey.withOpacity(0.4),
borderRadius: BorderRadius.circular(5),
),
child: Text(
stall.label,
style: const TextStyle(
color: Colors.white,
),
)),
const SizedBox(
width: 10,
),
],
)
],
),
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset(
stall.logoUrl,
width: 80,
),
),
],
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
stall.desc,
style: const TextStyle(fontSize: 16),
),
Row(
children: [
const Icon(
Icons.star_outline,
color: Colors.amber,
),
Text(
'${stall.score}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 15),
],
)
],
)
],
),
);
}
}
And this is stall
class Stall {
String name;
String label;
String logoUrl;
String desc;
num score;
Map<String, List<Food>> menu;
Stall(
this.name,
this.label,
this.logoUrl,
this.desc,
this.score,
this.menu,
);
static Stall generateRestaurant1() {
return Stall(
'Tray blazers',
'Restaurant',
'assets/images/tray_blazers.jpg',
'Tray Blazers by Chef Tracy',
4.5,
{
'Recommended': Food.generateRecommendedFoods1(),
'Popular': Food.generatePopularFoods1(),
'Smoothie': [],
'Rice': [],
},
);
}
}

If I understand the question correctly, you want to open the StallPage but show different values on the page depending on which image (pertaining to a given 'Stall') was selected on the previous page? I.e. clicking on item2 should open the StallPage with the restaurant title "Papa Rimz" etc.?
In that case, you can pass the argument to your new route builder via the onTap() function as a constructor parameter instead of calling Stall.generateRestaurant1() with hardcoded values in a given dart file.
StallInfo
Instead of getting your stall data inside the build method, you simply accept it as a required parameter for your widget. Now you have access to the data (title, ...) anywhere inside here.
class StallInfo extends StatelessWidget {
// Contains the stall object with its name, label, menu etc.
final Stall stall;
StallInfo({super.key, required this.stall});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40),
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
...
),
);
}
}
HomeScreen
I'm a bit confused as to what the item list in your your home screen is for. Are these food items in a restaurant? Because if so, I think it would be much easier to save them inside the stall as a list of items and then use that list here:
List<Stall> _stalls = [...];
I'd like to note here that you hardcoded all the items by name and then, in your build method, added them to a list. Since you don't need their names anywhere, it would be just a little bit better to move the List<Stall> myList outside the build method and simply assign the objects directly (that is, before you add a real database):
class GridDashboard extends StatelessWidget {
List<Stall> _stalls = [
Stall('Tray blazers', ...),
Stall('Papa Rimz', ...),
];
#override
Widget build(BuildContext context) {
// do something with your stalls, onTap, pass the element directly
....
children: _stalls.map(
(data) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: data)
));
}
);
}),
}
}
If you use a builder function for your GridView (which you should if there can be a lot of stalls), in the onTap() you can instead call:
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => StallPage(stall: _stalls.elementAt(index))
));
StallPage
This page will look something like this
class StallPage extends StatefulWidget {
final Stall stall; // Take in the stall you passed from your home screen
const StallPage({super.key, required this.stall});
#override
State<StallPage> createState() => _StallPageState();
}
class _StallPageState extends State<StallPage> {
var selected = 0;
final pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
...
StallInfo(stall: widget.stall), // This is how you can access the values passed inside a StatefulWidget
...
);
}
}

Related

How To Use Shared Preference In This Todo App Code?

import 'package:flutter/material.dart';
import '../main.dart';
import 'colors.dart';
import 'todo_item.dart';
import 'todo.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Toodoo extends StatefulWidget {
const Toodoo({Key? key}) : super(key: key);
#override
State<Toodoo> createState() => _ToodooState();
}
class _ToodooState extends State<Toodoo> {
final todosList = ToDo.todoList();
List<ToDo> _foundToDo = [];
final _todoController = TextEditingController();
final GlobalKey<ScaffoldState> _key = GlobalKey();
String ""
#override
void initState() {
_foundToDo = todosList;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.menu, color: Colors.black),
onPressed: () => _key.currentState!.openDrawer(),
),
backgroundColor: const Color(0xff346594),
title: const Text("ToDos", style: TextStyle(color: Colors.black)),
),
backgroundColor: tdBGColor,
body: Stack(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
child: Column(
children: [
searchBox(),
Expanded(
child: ListView(
children: [
Container(
margin: const EdgeInsets.only(
top: 50,
bottom: 20,
),
child: const Text(
'All ToDos',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
for (ToDo todo in _foundToDo.reversed)
ToDoItem(
todo: todo,
onToDoChanged: _handleToDoChange,
onDeleteItem: _deleteToDoItem,
),
],
),
)
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: Row(children: [
Expanded(
child: Container(
margin: const EdgeInsets.only(
bottom: 20,
right: 20,
left: 20,
),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 0.0),
blurRadius: 10.0,
spreadRadius: 0.0,
),
],
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: _todoController,
decoration: const InputDecoration(
hintText: 'Add a new todo item',
border: InputBorder.none),
),
),
),
Container(
margin: const EdgeInsets.only(
bottom: 20,
right: 20,
),
child: ElevatedButton(
onPressed: () {
_addToDoItem(_todoController.text);
},
style: ElevatedButton.styleFrom(
backgroundColor: tdBlue,
minimumSize: const Size(60, 60),
elevation: 10,
),
child: const Text('+', style: TextStyle(fontSize: 40),),
),
),
]),
),
],
),
drawer: const Navigation(),
);
}
void _handleToDoChange(ToDo todo) {
setState(() {
todo.isDone = !todo.isDone;
});
}
void _deleteToDoItem(String id) {
setState(() {
todosList.removeWhere((item) => item.id == id);
});
}
void _addToDoItem(String toDo) async{
final sp = await SharedPreferences.getInstance();
setState(() {
todosList.add(ToDo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
todoText: toDo,
));
});
sp.setString(id, todo)
_todoController.clear();
}
void _runFilter(String enteredKeyword) {
List<ToDo> results = [];
if (enteredKeyword.isEmpty) {
results = todosList;
} else {
results = todosList
.where((item) => item.todoText!
.toLowerCase()
.contains(enteredKeyword.toLowerCase()))
.toList();
}
setState(() {
_foundToDo = results;
});
}
Widget searchBox() {
return Container(
);
}
}
I am trying to save todo data locally, using shared preferences but don't know how to implement this, any help on this will be appreciated.Shared preferences is the best thing to use in such apps, so that's why I am using shared preference instead of firebase.
I have initialized Shared preferences in future but the thing is how to read and show the data with the controller given above the code.
Use Hive database or sqflite to save such kind of data(Good practice).You should use shared preference to store small bunch of data.
Yeah, shared preference is a good way to store data permanently on the local device. I can suggest you one way of doing this.
You need to create only one key (and value), and the value would be a stringified array. Every time user created new todo, you first need to pull the previous array, parse it to JSON, push the latest todo in that array, and set the key value again.
This array will also help you if you want to show the user all the todos by pulling the data only from one key, cause all the todos will the store in one array.
var todos = [
{
"id": "",
"todoText": "''"
},
{
"id": "",
"todoText": "''"
},
...
]
But you need to store stringified array, so you need to parse back to JSON after get data from shared preferences

How do I prevent an endless Listview scroll of product data being repeated on my product screens? [Flutter]

I'm working on a shopping app for a school project, and I'm trying to get my product images and details to show up on the various product detail screens. I used some sample images from flutter's Shrine sample app as I followed their tutorial. The problem I'm facing now is an endless Listview scroll that looks like this, and when you keep scrolling, it repeats the image and details endlessly:
What should I do to avoid this? Sorry, I am really new to coding so I am not to sure how to go about fixing this problem...Below I have included a few dart files of my current code which might be helpful. Thank you to anyone who is willing to help, it is very much appreciated!
Product detail screen dart file:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:MyShoppingApp/provider/CartProvider.dart';
import 'package:MyShoppingApp/db/cart_database.dart';
import 'package:MyShoppingApp/model/cart.dart';
import 'model/products_repository.dart';
import '../model/cart.dart';
class ProductDetailsPage extends StatelessWidget {
static const routeName = '/user-products';
ProductDetailsPage({Key? key}) : super(key: key); //const
DBHelper dbHelper = DBHelper();
#override
Widget build(BuildContext context) {
//get particular productId using the ModalRoute class
final productId = ModalRoute.of(context)!.settings.arguments as String;
print(productId);
//use Provider package to find out ID by accessing method declared in Product()
final loadedProduct = ProductsRepository().findById(productId);
//List<bool> clicked = List.generate(10, (index) => false, growable: true);
final cart = Provider.of<CartProvider>(context);
void saveData(int index) {
dbHelper
.insert(
CartItem(
id: index,
title: loadedProduct.name,
price: loadedProduct.price.toDouble(),
quantity: ValueNotifier(1),
image: loadedProduct.image,
),
)
.then((value) {
cart.addTotalPrice(loadedProduct.price.toDouble());
cart.addCounter();
print('Product Added to cart');
}).onError((error, stackTrace) {
print(error.toString());
});
}
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: loadedProduct.length,
itemBuilder: (context, index) {
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
loadedProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${loadedProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
loadedProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
}
}
Product Repository Dart file:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:MyShoppingApp/db/cart_database.dart';
//add product data
import 'package:MyShoppingApp/model/product.dart';
//to get all products at once or any particular product by its ID
//product class that uses mixins with ChangeNotifier
class ProductsRepository with ChangeNotifier {
DBHelper dbHelper = DBHelper();
static List<Product> loadProducts(Category category) {
//linked list storing objects of type Product
var allProducts = <Product>[
Product(
category: Category.accessories,
id: "0",
isFeatured: true,
name: 'Vagabond sack',
price: 120,
details: "Nice fancy shirt",
description: "Comfortable and minimalistic",
image: "packages/shrine_images/0-0.jpg",
),
Product(
category: Category.accessories,
id: "1",
isFeatured: true,
name: 'Stella sunglasses',
price: 58,
details: "",
description: "",
image: "packages/shrine_images/1-0.jpg",
),
Product(
category: Category.accessories,
id: "2",
isFeatured: false,
name: 'Whitney belt',
price: 35,
details: "",
description: "",
image: "packages/shrine_images/2-0.jpg",
),
Product(
category: Category.accessories,
id: "3",
isFeatured: true,
name: 'Garden strand',
price: 98,
details: "",
description: "",
image: "packages/shrine_images/3-0.jpg",
),
Product(
category: Category.accessories,
id: "4",
isFeatured: false,
name: 'Strut earrings',
price: 34,
details: "",
description: "",
image: "packages/shrine_images/4-0.jpg",
),//removed other products to save space
];
if (category == Category.all) {
return allProducts;
} else {
return allProducts.where((Product p) {
return p.category == category;
}).toList();
}
}
//to get particular products by ID
Product findById(String id) {
var x = loadProducts(Category.all).firstWhere((prod) => prod.id == id);
print("findById successful");
print(x);
return x;
}
void addProduct() {
// _items.add(value);
notifyListeners();
}
}
Thank you everyone! Also, this problem stemmed from an earlier error in another post that a user was helping me to work through, but I didn't want to trouble them too much so I am reposting it here. Thank you #eamirho3einπŸ™πŸ™
Try this:
class ProductDetailsPage extends StatefulWidget {
static const routeName = '/user-products';
ProductDetailsPage({Key? key}) : super(key: key);
#override
State<ProductDetailsPage> createState() => _ProductDetailsPageState();
}
class _ProductDetailsPageState extends State<ProductDetailsPage> {
//const
DBHelper dbHelper = DBHelper();
List<Product> loadedProduct = []; // <----update this
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
//get particular productId using the ModalRoute class
final productId = ModalRoute.of(context)!.settings.arguments as String;
print(productId);
//use Provider package to find out ID by accessing method declared in Product()
setState(() {
loadedProduct = ProductsRepository().findById(productId);
});
});
}
#override
Widget build(BuildContext context) {
//List<bool> clicked = List.generate(10, (index) => false, growable: true);
final cart = Provider.of<CartProvider>(context);
void saveData(int index) {
dbHelper
.insert(
CartItem(
id: index,
title: loadedProduct.name,
price: loadedProduct.price.toDouble(),
quantity: ValueNotifier(1),
image: loadedProduct.image,
),
)
.then((value) {
cart.addTotalPrice(loadedProduct.price.toDouble());
cart.addCounter();
print('Product Added to cart');
}).onError((error, stackTrace) {
print(error.toString());
});
}
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: loadedProduct.length,
itemBuilder: (context, index) {
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
loadedProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${loadedProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
loadedProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
}
}
Hello I see some weird thing there.
ProductsRepository().findById(productId) return a single Product. Then
loadedProduct is a Product. Later on the ListView.builder on the itemCount parameter you used loadedProduct.length (loadedProduct in this point is used as a list of element). I dont know if the Product object have a .length method nor what returns that. But i think there is the problem. When you set itemCount: loadedProduct.length you are saying than the list will have x elements and later on the itemBuilder you are only using the data from loadedProduct to create the same widget in the list, x times.
Assuming that loadedProduct.length = 10, you will have a list view with the same Element 10 times
The next example use the loadedProduct to fill the products list. The product list will be used as the data on the ListView.builder. Then the list will show 3 time the same widget.
final products = [
loadedProduct,
loadedProduct,
loadedProduct
];
return Scaffold(
backgroundColor: Colors.orange[50],
appBar: AppBar(
backgroundColor: Colors.deepOrange[900],
title: const Text("Product details "),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_outlined,
color: Colors.black,
semanticLabel: 'back to home',
),
onPressed: () {
Navigator.pop(context);
},
),
),
//body:
body: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 8.0),
shrinkWrap: true,
itemCount: products.length,
itemBuilder: (context, index) {
final targetProduct = products[index];
return Card(
//SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 300,
width: double.infinity,
child: Image.asset(
targetProduct.image,
fit: BoxFit.cover,
),
),
const SizedBox(height: 10),
Text(
'\$${targetProduct.price}',
style: const TextStyle(
color: Colors.grey,
fontSize: 20,
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.blueGrey.shade900),
onPressed: () {
saveData(Random().nextInt(1000));
},
child: const Text('Add to Cart')),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
width: double.infinity,
child: Text(
targetProduct.description,
textAlign: TextAlign.center,
softWrap: true,
),
),
],
),
);
})
);
Just think what do you want to show on the list and use the right data. And review why the Product object have a .length method and what it return.
if you want to stop scroll listview add
NeverScrollableScrollPhysics
physics: const NeverScrollableScrollPhysics()
Inside ListView widget, use

Creating a Dynamic list of animated containers that can have different properties

I am trying to implement this design where one chip is clickable at a time
Design Image
I tried multiple approaches like listview and grid view builders, but nothing gave me precisely what I was looking for. I eventually settled for the Wrap() widget with a list used to map Animated Container widgets, it gave me the look I wanted, but when I clicked on one chip, everything changes color instead of one like this.
How I can implement those grids of chips dynamically with the property of only one chip changing color at a time and from there be able to navigate to the next page. For more context, this is the code I used,
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:scree/constants.dart';
class SiteAbooutScreen extends StatefulWidget {
const SiteAbooutScreen({Key? key}) : super(key: key);
#override
_SiteAbooutScreenState createState() => _SiteAbooutScreenState();
}
class _SiteAbooutScreenState extends State<SiteAbooutScreen> {
bool _isTapped = false;
#override Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 74.75),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
runSpacing: 16,
children: _stuff
.map(
(strings) => GestureDetector(
onTap: () {
setState(() {
_isTapped = !_isTapped;
});
},
child: AnimatedContainer(
padding:
EdgeInsets.symmetric(horizontal: 24, vertical: 10),
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
color: _isTapped ? primary1 : Colors.transparent,
border: Border.all(color: Colors.black54),
borderRadius: BorderRadius.circular(100)),
child: Text(
strings,
style: Small.copyWith(
fontSize: 14,
color:
_isTapped ? Colors.white : Color(0xff929292)),
),
),
),
)
.toList(),
)
],
),
),
);
}
List<String> _stuff = [
'Portfolio',
'Art',
'Marketing',
'Education',
'Blog',
'Travel',
'Fashion',
'Beauty',
'Design',
'Online Store',
'Fitness',
'Food'
];
}
This is happening because you are using single bool to handle every item. That's why everything change at the same time. In order to handle one at a time, you need to check separately.
For this, you can create list of bool or String for selected items.
class SiteAbooutScreen extends StatefulWidget {
const SiteAbooutScreen({Key? key}) : super(key: key);
#override
_SiteAbooutScreenState createState() => _SiteAbooutScreenState();
}
class _SiteAbooutScreenState extends State<SiteAbooutScreen> {
List<String> tappedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 74.75),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 16,
runSpacing: 16,
children: _stuff
.map(
(strings) => GestureDetector(
onTap: () {
setState(() {
if (tappedItems.contains(strings)) {
tappedItems.remove(strings);
} else {
tappedItems.add(strings);
}
});
},
child: AnimatedContainer(
padding:
EdgeInsets.symmetric(horizontal: 24, vertical: 10),
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
color: tappedItems.contains(strings)
? Theme.of(context).primaryColor
: Colors.transparent,
border: Border.all(color: Colors.black54),
borderRadius: BorderRadius.circular(100)),
child: Text(
strings,
style: TextStyle(
fontSize: 14,
color: tappedItems.contains(strings)
? Colors.white
: Color(0xff929292)),
),
),
),
)
.toList(),
)
],
),
),
);
}
List<String> _stuff = [
'Portfolio',
'Art',
'Marketing',
'Education',
'Blog',
'Travel',
'Fashion',
'Beauty',
'Design',
'Online Store',
'Fitness',
'Food'
];
}

How to add another List of item for DropDownMenu, and then use the items in widget

So I'm trying to make a drop down menu for each options and insert a different List of items each. First of all, because my Dropdownmenu widget shares the same properties for one and another, I extracted the widget to another class name "MenuDropDown". Here is the code for the widget.
import 'package:flutter/material.dart';
import 'List.dart';
class MenuDropDown extends StatefulWidget {
final String dropdownText;
final List<DropdownMenuItem<String>> itemList;
MenuDropDown({this.dropdownText, this.itemList});
#override
_MenuDropDownState createState() => _MenuDropDownState();
}
class _MenuDropDownState extends State<MenuDropDown> {
String selectedItem;
List<DropdownMenuItem> getGroomingTypeList() {
List<DropdownMenuItem<String>> dropdownItems = [];
for (String groomingType in groomingTypeList) {
var newItem = DropdownMenuItem(
child: Text(groomingType),
value: groomingType,
);
dropdownItems.add(newItem);
}
return dropdownItems;
}
List<DropdownMenuItem> getCatBreedsList() {
List<DropdownMenuItem<String>> dropdownItems = [];
for (String catBreed in catBreedsList) {
var newItem = DropdownMenuItem(
child: Text(catBreed),
value: catBreed,
);
dropdownItems.add(newItem);
}
return dropdownItems;
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 10.0),
child: Container(
width: 325.0,
height: 50.0,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black45,
offset: Offset(2.5, 5.5),
blurRadius: 5.0,
)
],
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: selectedItem,
hint: Padding(
padding: const EdgeInsets.fromLTRB(22.0, 0, 0, 0),
child: Text(
widget.dropdownText,
style: TextStyle(),
),
),
items: widget.itemList,
onChanged: (value) {
setState(() {
selectedItem = value;
});
}),
),
),
);
}
}
above here I created a method to get the list item from the list class that I already created, First it works if I hardcoded the method into the Dropdownmenu's items properties to show the item list, but because I need to use the different list for the different widget, so I think if I try to create a variable of List named itemList, so I can access it from the other class where I can call just the customized variable.
And this is the Stateful widget where i use my Extracted Dropdownmenu widget :
import 'package:flutter/material.dart';
import 'TitleName.dart';
import 'dropdownmenu.dart';
class InformationDetail extends StatefulWidget {
#override
_InformationDetailState createState() => _InformationDetailState();
}
class _InformationDetailState extends State<InformationDetail> {
MenuDropDown _menuDropDown = MenuDropDown();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(25.0, 68.0, 70.0, 26.0),
child: Text(
'Information Detail',
style: TextStyle(fontSize: 35.0),
),
),
Column(
// Wrap Column
children: <Widget>[
Column(
children: <Widget>[
TitleName(
titleText: 'Grooming Type',
infoIcon: Icons.info,
),
MenuDropDown(
dropdownText: 'Grooming Type...',
//I'm trying to implement the list here with a custom variable that contain
a method with different list in dropdownmenu class
itemlist: getGroomingTypeList(),
),
TitleName(
titleText: 'Cat Breeds',
),
MenuDropDown(
dropdownText: 'Cat Breeds...',
),
TitleName(
titleText: 'Cat Size',
infoIcon: Icons.info,
),
MenuDropDown(
dropdownText: 'Cat Size...',
),
TitleName(
titleText: 'Add-On Services',
),
MenuDropDown(
dropdownText: 'Add - On Services...',
),
Container(
margin: EdgeInsets.fromLTRB(0, 15.0, 0, 0),
width: 75.0,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
border: Border.all(
color: Colors.black,
),
borderRadius: BorderRadius.circular(12.0),
),
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {
Navigator.of(context)
.pushNamed('/ReservationDetail');
},
),
),
],
),
],
),
],
),
),
),
);
}
}
And this is the list that I want to use for each of dropdown menu widget
const List groomingTypeList = ['Basic Grooming', 'Full Grooming'];
const List catBreedsList = [
'Persia',
'Anggora',
'Domestic',
'Maine Coon',
'Russian Blue',
'Slamese',
'Munchkin',
'Ragdoll',
'Scottish Fold',
];
const List catSizeList = [
'Small Size',
'Medium Size',
'Large Size',
'Extra Large Size',
];
const List addOnServicesList = [
'Spa & Massage',
'Shaving Hair / Styling',
'Injection Vitamis Skin & Coat',
'Cleaning Pet House and Environment',
'Fur Tangled Treatment',
];
I got stuck from there. How to use a different list for each dropdown menu widget that I made, because I already tried to make a constructor, variable, and method so I can use it separately, but instead I got an error that says,
type 'List' is not a subtype of type 'List<DropdownMenuItem>'
I think I implemented it in the wrong way in the first place. I really need help with another solution to these problems. thank you, everyone!
Just check out the code that I have made some changes :
import 'package:flutter/material.dart';
void main() => runApp(InformationDetail());
class InformationDetail extends StatefulWidget {
#override
_InformationDetailState createState() => _InformationDetailState();
}
class _InformationDetailState extends State<InformationDetail> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(25.0, 68.0, 70.0, 26.0),
child: Text(
'Information Detail',
style: TextStyle(fontSize: 35.0),
),
),
Column(
// Wrap Column
children: <Widget>[
Column(
children: <Widget>[
Text(
'Grooming Type',
),
MenuDropDown(
dropdownText: 'Grooming Type...',
//I'm trying to implement the list here with a custom variable that contain
// a method with different list in dropdownmenu class
type: "groomingType",
),
Text(
'Cat Breeds',
),
MenuDropDown(
dropdownText: 'Cat Breeds...',
type: "catBreeds",
),
Text(
'Cat Size',
),
MenuDropDown(
dropdownText: 'Cat Size...',
type: "catSize",
),
Text(
'Add-On Services',
),
MenuDropDown(
dropdownText: 'Add - On Services...',
type: "addOnServices",
),
Container(
margin: EdgeInsets.fromLTRB(0, 15.0, 0, 0),
width: 75.0,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
border: Border.all(
color: Colors.black,
),
borderRadius: BorderRadius.circular(12.0),
),
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {
Navigator.of(context)
.pushNamed('/ReservationDetail');
},
),
),
],
),
],
),
],
),
),
),
),
);
}
}
class MenuDropDown extends StatefulWidget {
final String dropdownText;
final String type;
MenuDropDown({this.dropdownText, this.type});
#override
_MenuDropDownState createState() => _MenuDropDownState();
}
class _MenuDropDownState extends State<MenuDropDown> {
String selectedItem;
List<String> dropdownItems = [];
List<String> groomingTypeList = ['Basic Grooming', 'Full Grooming'];
List<String> catBreedsList = [
'Persia',
'Anggora',
'Domestic',
'Maine Coon',
'Russian Blue',
'Slamese',
'Munchkin',
'Ragdoll',
'Scottish Fold',
];
List<String> catSizeList = [
'Small Size',
'Medium Size',
'Large Size',
'Extra Large Size',
];
List<String> addOnServicesList = [
'Spa & Massage',
'Shaving Hair / Styling',
'Injection Vitamis Skin & Coat',
'Cleaning Pet House and Environment',
'Fur Tangled Treatment',
];
List<String> getListBasedOnName(String value) {
print(value);
switch (value) {
case "groomingType":
return groomingTypeList;
break;
case "catBreeds":
return catBreedsList;
break;
case "catSize":
return catSizeList;
break;
case "addOnServices":
return addOnServicesList;
break;
}
return null;
}
#override
void initState() {
super.initState();
print(widget.type);
dropdownItems = getListBasedOnName(widget.type);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 10.0),
child: Container(
width: 325.0,
height: 50.0,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black45,
offset: Offset(2.5, 5.5),
blurRadius: 5.0,
)
],
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: selectedItem,
hint: Padding(
padding: const EdgeInsets.fromLTRB(22.0, 0, 0, 0),
child: Text(
widget.dropdownText,
style: TextStyle(),
),
),
items: dropdownItems.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedItem = value;
});
}),
),
),
);
}
}
As some of the widgets were missing, I have added mine you can change as per your needs. Let me know if it works.

How to pass data back from a widget?

I have a screen where users can add a location. Here, I have separated all my widgets into there own files as illustrated below;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/location/location_help_screen.dart';
import 'package:fluttershare/widgets/common_widgets/customDivider.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/camp_type_select.dart';
import '../../widgets/extra_location_notes.dart';
import '../../widgets/location_input.dart';
import '../../widgets/opening_times.dart';
import '../../widgets/post_media.dart';
import '../../widgets/space_avalibility.dart';
import '../../widgets/utility_type_select.dart';
import '../../widgets/width_restriction.dart';
import '../../widgets/height_restriction.dart';
import '../../models/locations.dart';
import '../../models/user.dart';
import '../home.dart';
class AddNewLocation extends StatefulWidget {
static const routeName = '/add-new-location';
final User currentUser;
AddNewLocation({this.currentUser});
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
String postId = Uuid().v4();
final _scaffoldKey = GlobalKey<ScaffoldState>();
PlaceLocation _pickedLocation;
int storyPostCount = 0;
bool isLoading = false;
void _selectPlace(double lat, double lng) {
_pickedLocation = PlaceLocation(lattitude: lat, longitude: lng);
}
getLocationPostCount() async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await locationPostRef
.document(currentUser.id)
.collection('user_location_posts')
.getDocuments();
setState(() {
storyPostCount = snapshot.documents.length;
});
}
createLocationPostInFirestore(
{String mediaUrl,
String description,
double heightRestriction,
double widthRestriction}) {
locationPostRef
.document(currentUser.id)
.collection("user_location_posts")
.document(postId)
.setData({
"postId": postId,
"ownerId": currentUser.id,
"username": currentUser.username,
"description": description,
"timestamp": timestamp,
"lattitude": _pickedLocation.lattitude,
"longitude": _pickedLocation.longitude,
"max_height": heightRestrictionValue.toStringAsFixed(0),
"max_width": widthRestrictionValue.toStringAsFixed(0),
});
}
handlePostSubmit() {
createLocationPostInFirestore(
heightRestriction: heightRestrictionValue,
widthRestriction: widthRestrictionValue,
);
SnackBar snackbar = SnackBar(
content: Text("Profile Updated"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
setState(() {
postId = Uuid().v4();
});
}
buildUploadUserHeader() {
return Container(
margin: EdgeInsets.only(bottom: 10),
height: 200,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(currentUser.photoUrl)),
),
],
),
),
),
Expanded(
flex: 6,
child: Container(
color: Colors.pink,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(currentUser.displayName),
],
),
),
),
],
),
);
}
buildCampUploadForm() {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//buildUploadUserHeader(), //TODO: This is the profile header that is dissabled for now. Work on possibly a header in the future.
Container(
padding: EdgeInsets.all(15),
child: Column(
children: <Widget>[
CampTypeSelect(),
CustomDivider(),
LocationInput(_selectPlace),
CustomDivider(),
HeightRestriction(),
WidthRestriction(),
SpaceAvalibility(),
OpeningTimes(),
CustomDivider(),
PostMedia(),
CustomDivider(),
UtilityServices(),
CustomDivider(),
ExtraLocationNotes(),
Container(
height: 80,
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: FlatButton(
color: Colors.black,
onPressed: () => handlePostSubmit(),
child: Text(
"SUBMIT",
style: Theme.of(context).textTheme.display2,
),
padding: EdgeInsets.all(20),
),
)
],
),
),
],
),
),
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text(
'Add New Location',
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.info_outline),
color: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => LocationSubmitHelpScreen()),
);
},
),
// action button
IconButton(
icon: Icon(Icons.close),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
body: buildCampUploadForm(),
backgroundColor: Colors.white,
);
}
}
What I am trying to do is pass the data back from the widget ExtraLocationNotes()
to the function createLocationPostInFirestore().
For context, this is what my widget looks like;
import 'package:flutter/material.dart';
import 'common_widgets/custom_form_card.dart';
class ExtraLocationNotes extends StatefulWidget {
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
return CustomFormCard(
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Text(
"EXTRA INFORMATION",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w400,
letterSpacing: 2.0,
),
),
],
),
),
SizedBox(height: 20),
TextFormField(
controller: descriptionController,
maxLines: 6,
maxLength: 250,
maxLengthEnforced: true,
style:
new TextStyle(fontSize: 18.0, height: 1.3, color: Colors.black),
decoration: const InputDecoration(
hintText:
"Please write a description of this location for fellow travellers.",
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(),
borderSide: BorderSide(color: Colors.black),
),
),
),
],
),
);
}
}
How do I pass the data back to the parent widget?
You need a callback, which will be triggered in the child widget then the value will be updated in the parent widget:
// 1- Define a pointers to executable code in memory, which is the callback.
typedef void MyCallback(String val);
class ExtraLocationNotes extends StatefulWidget {
// 2- You will pass it to this widget with the constructor.
final MyCallback cb;
// 3- ..pass it to this widget with the constructor
ExtraLocationNotes({this.cb});
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
//..
//...
RaisedButton(
//..
// 4- in any event inside the child you can call the callback with
// the data you want to send back to the parent widget:
onPressed: () {
widget.cb("Hello from the other side!");
}
),
}
Then inside the parent widget you need to catch the data which sent form the child:
class AddNewLocation extends StatefulWidget {
//...
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
// 1- Global var to store the data that we're waiting for.
String _dataFromMyChild = "";
buildCampUploadForm() {
return Container(
//...
//...
// 2- Pass the callback with the constructor of the child, this
// will update _dataFromMyChild's value:
ExtraLocationNotes(cb: (v) => setState(() => _dataFromMyChild = v)),
//..
}
// then
createLocationPostInFirestore() {
// Use _dataFromMyChild's value here
}
}
You can use the BuildContext object to get the context widget (might no be the parent!) couldn't read it all but as i understand that you need to pass the info from the child to the parent ,and you can do it with some like this :-
(context.widget as MyType).doStuff();
Note.
please check first with
print(context.widget.runtimeType);
but to make a better solution make a mutable data object that is passed from parent to the child so when changes happens it reflect's on the parent so you can separate business logic from ui logic.