Related
I'm trying to write a shopping app. But unfortunately I stuck with loading and displaying the products from firebase with the help of the package provider.
In the app you can get from the searchPage to the ResultPage. In the ResultPage a GridView.builder should display the products whose category matches your entered searchtext from the SearchPage.
From the ResultPage you can also get to a FilterScreen where you can set filters. After you have left this screen you should only see the products on the result screen which matches your filters.
But this isn't working at all. Instead of seeing the filtered products there are no products displayed. But I get no error message. So I really stuck at this problem.
I don't even know where it's coming from.
I would be very thankful if anyone could help me!
This is my result page code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shs/providers/filters_provider.dart';
import 'package:shs/providers/products_provider.dart';
import 'package:shs/screens/filters_screen.dart';
import 'package:shs/widgets/bottom_navigation_bar.dart';
import 'package:shs/widgets/filter_overlay.dart';
import 'package:shs/widgets/my_scaffold.dart';
import 'package:shs/widgets/product_grid.dart';
import '../widgets/size_grid.dart';
import '../widgets/size_grid_buchstaben.dart';
class ResultPage extends StatefulWidget {
String searchtext;
String? gender;
ResultPage(
this.searchtext,
this.gender,
);
#override
State<ResultPage> createState() => _ResultPageState();
}
class _ResultPageState extends State<ResultPage> {
final searchController = TextEditingController();
#override
void initState() {
searchController.addListener(() => setState(() {}));
super.initState();
}
#override
Widget build(BuildContext context) {
return MyScaffold(
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 30,
),
Container(
height: 47,
margin: EdgeInsets.only(top: 30, left: 30, right: 30),
child: TextField(
onSubmitted: ((value) {
setState(() {
widget.searchtext = value;
});
}),
controller: searchController,
textInputAction: TextInputAction.search,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
fillColor: Colors.white,
suffixIcon: searchController.text.isEmpty
? Container(
width: 0,
)
: IconButton(
onPressed: () {
searchController.clear();
},
icon: Icon(
Icons.close_rounded,
color: Colors.black,
)),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(15)),
prefixIcon: Container(
padding: const EdgeInsets.only(top: 5, left: 8),
child: Image.asset(
'lib/asset/icons/Search.png',
),
),
hintText: 'Search',
hintStyle: Theme.of(context).textTheme.bodyMedium,
),
),
),
SizedBox(
height: 30,
),
Padding(
padding: const EdgeInsets.only(right: 230),
child: Container(
width: 109,
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Theme.of(context).colorScheme.primary,
),
child: GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(FiltersScreen.routeName);
},
child: Row(
children: [
SizedBox(
width: 10,
),
Icon(Icons.filter_list_alt),
SizedBox(
width: 10,
),
Text(
'Filter',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontSize: 15),
)
],
),
)),
),
SizedBox(
height: 30,
),
Consumer<FilterProvider>(
builder: (context, filters, _) => ProductGrid(
filterColor: filters.filterMap['color']!,
filterZustand: filters.filterMap['state']!,
filtersize: filters.filterMap['size']!,
filterProdCat: widget.searchtext,
filterprice: filters.filteredPrice,
)), //Marke noch überall hnzufügen, wenn Filter eingefügt + searchtext zur filters.filterList hinzufügen und wie die anderen darauf referenzieeren, Suchfeld für alle Attribute verwendbar machen
SizedBox(
height: 30,
),
MyBottomNavigationBar(),
],
)),
);
}
}
This is my FilterProviderCode
import 'package:flutter/material.dart';
class FilterProvider with ChangeNotifier {
Map<String, List<String>> _filterMap = {'size': [], 'state': [], 'color': []};
double? filteredPrice;
var _expandedSize = false;
var _expandedPrice = false;
var _expandedState = false;
var _expandedColor = false;
var _expandedBrand = false;
bool _selected = false;
bool get selected {
return _selected;
}
bool _selectedSize = false;
bool _selectedState1 = false;
bool _selectedState2 = false;
bool _selectedState3 = false;
bool _selectedState4 = false;
bool _selectedState5 = false;
bool _selectedColor1 = false;
bool _selectedColor2 = false;
bool _selectedColor3 = false;
bool _selectedColor4 = false;
bool _selectedColor5 = false;
bool _selectedColor6 = false;
bool _selectedColor7 = false;
bool _selectedColor8 = false;
bool get selectedSize {
return _selectedSize;
}
Map<String, List<String>> get filterMap {
return _filterMap;
}
void setSelectedSize(bool variable) {
_selectedSize = variable;
}
void setSelectedState1(bool variable) {
_selectedState1 = variable;
}
void setSelectedState2(bool variable) {
_selectedState2 = variable;
}
void setSelectedState3(bool variable) {
_selectedState3 = variable;
}
void setSelectedState4(bool variable) {
_selectedState4 = variable;
}
void setSelectedState5(bool variable) {
_selectedState5 = variable;
}
void setSelectedColor1(bool variable) {
_selectedColor1 = variable;
}
void setSelectedColor2(bool variable) {
_selectedColor2 = variable;
}
void setSelectedColor3(bool variable) {
_selectedColor3 = variable;
}
void setSelectedColor4(bool variable) {
_selectedColor4 = variable;
}
void setSelectedColor5(bool variable) {
_selectedColor5 = variable;
}
void setSelectedColor6(bool variable) {
_selectedColor6 = variable;
}
void setSelectedColor7(bool variable) {
_selectedColor7 = variable;
}
void setSelectedColor8(bool variable) {
_selectedColor8 = variable;
}
bool get selectedState1 {
return _selectedState1;
}
bool get selectedState2 {
return _selectedState2;
}
bool get selectedState3 {
return _selectedState3;
}
bool get selectedState4 {
return _selectedState4;
}
bool get selectedState5 {
return _selectedState5;
}
bool get selectedColor1 {
return _selectedColor1;
}
bool get selectedColor2 {
return _selectedColor2;
}
bool get selectedColor3 {
return _selectedColor3;
}
bool get selectedColor4 {
return _selectedColor4;
}
bool get selectedColor5 {
return _selectedColor5;
}
bool get selectedColor6 {
return _selectedColor6;
}
bool get selectedColor7 {
return _selectedColor7;
}
bool get selectedColor8 {
return _selectedColor8;
}
bool get expandedSize {
return _expandedSize;
}
bool get expandedPrice {
return _expandedPrice;
}
bool get expandedState {
return _expandedState;
}
bool get expandedColor {
return _expandedColor;
}
bool get expandedBrand {
return _expandedBrand;
}
void expandSize() {
_expandedSize = !_expandedSize;
notifyListeners();
}
void expandPrice() {
_expandedPrice = !_expandedPrice;
notifyListeners();
}
void expandState() {
_expandedState = !_expandedState;
notifyListeners();
}
void expandColor() {
_expandedColor = !_expandedColor;
notifyListeners();
}
void expandBrand() {
_expandedBrand = !_expandedBrand;
notifyListeners();
}
void applyFilter(String key, String entry) {
_selected = !_selected;
if (_selected) {
_filterMap[key]!.add(entry);
}
if (_selected == false) {
_filterMap[key]!.removeWhere((element) => element == entry);
}
print(_filterMap);
notifyListeners();
}
void applyFilterUpdate(double entry) {
filteredPrice = entry;
notifyListeners();
print(filteredPrice);
}
}
This is my ProductProvider Code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:shs/models/product.dart';
class ProductProvider with ChangeNotifier {
final db = FirebaseFirestore.instance;
List<Product> _items = [];
List<Product> get items {
return [..._items];
}
/*Stream<QuerySnapshot> get stream {
return db
.collection('products')
.withConverter(
fromFirestore: Product.fromFirestore,
toFirestore: (Product product, _) => product.toFirestore(),
)
.snapshots();
}*/
Future<void> fetchProducts(
{required bool showAll,
List<dynamic>?
filtersize, //List ist halt manchmal leer wie bei allen anderen
double? filterprice,
List<dynamic>? filterZustand,
//required List<String>? filterMarke,
List<dynamic>? filterColor,
String? filterProdCat}) async {
List<Product> filteredProducts = [];
List<Product> allProducts = [];
List<String> allIDs = [];
final ref = db.collection('products').withConverter(
fromFirestore: Product.fromFirestore,
toFirestore: (Product product, _) => product.toFirestore(),
);
ref.snapshots().listen((event) {
List<String> products = [];
for (var doc in event.docs) {
products.add(doc.id);
allIDs = products;
}
});
allIDs.forEach((id) async {
final docSnap = await ref.doc(id).get();
final product = docSnap.data();
if (product != null) {
allProducts.add(product);
} else {
print('No such document.');
}
});
filteredProducts = allProducts.where((product) {
return filtersize!.contains(product
.size) || //Liste ist halt manchmal leer, also nur wahr wenn auch wirklich Filter gesetzt sind
product.price == filterprice ||
filterZustand!.contains(product.zustand) ||
//filterMarke.contains(product.marke) ||
filterColor!.contains(product.color) ||
product.prodCat == filterProdCat;
}).toList(); // nur wenn filtern erwünscht alle Filter Lists angeben, auch wenn sie leer sind sonst null
if (showAll) {
_items = allProducts;
} else {
_items = filteredProducts;
}
//notifyListeners();
}
}
This is my FiltersScreen Code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shs/providers/filters_provider.dart';
import 'package:shs/widgets/filter_color.dart';
import 'package:shs/widgets/filter_state.dart';
import 'package:shs/widgets/marked_text.dart';
import 'package:shs/widgets/size_grid.dart';
import 'package:shs/widgets/size_grid_buchstaben.dart';
import 'package:shs/widgets/size_grid_hosen.dart';
import 'package:shs/widgets/size_grid_shoes.dart';
class FiltersScreen extends StatefulWidget {
static const routeName = '/filters';
#override
State<FiltersScreen> createState() => _FiltersScreenState();
}
class _FiltersScreenState extends State<FiltersScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.close,
size: 50,
),
onPressed: () {
Navigator.of(context).pop();
},
),
backgroundColor: Theme.of(context).colorScheme.primary,
actions: [
GestureDetector(
onTap: () {}, // Navigate to AccountPage
child: Container(
child: Image.asset('lib/asset/icons/Account.png'),
),
),
SizedBox(
width: 7,
),
GestureDetector(
onTap: () {}, // Navigate to ShoppingBag
child: Container(
child: Image.asset('lib/asset/icons/ShoppingBag.png'),
),
),
GestureDetector(
onTap: () {}, // Navigate to Favorite
child: Container(
child: Image.asset('lib/asset/icons/Favorite.png'),
),
),
SizedBox(
width: 7,
),
],
),
body: Consumer<FilterProvider>(
builder: (context, filters, _) => SingleChildScrollView(
child: Column(children: [
SizedBox(
height: 30,
),
Center(
child: MarkedText(
'Filters',
() {},
Theme.of(context).colorScheme.primary,
fontsize: 80,
),
),
SizedBox(
height: 30,
),
ListTile(
title: Text(
'Size',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
trailing: IconButton(
onPressed: () {
filters.expandSize();
},
icon: Icon(
filters.expandedSize
? Icons.expand_less
: Icons.expand_more,
size: 50,
color: Colors.black)),
),
if (filters.expandedSize)
Container(
height: 1800,
child: Column(
children: [
SizedBox(
height: 30,
),
SizedBox(
height: 300,
child: Container(
width: 300, height: 500, child: SizeGrid())),
SizedBox(
height: 30,
),
SizedBox(
height: 230,
child:
Container(width: 300, child: SizeGridBuchstaben())),
SizedBox(
height: 30,
),
SizedBox(
height: 450,
child: Container(width: 300, child: SizeGridHosen())),
SizedBox(
height: 30,
),
Text(
'Size Shoes',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
SizedBox(
height: 30,
),
SizedBox(
height: 530,
child: Container(width: 300, child: SizeGridShoes())),
SizedBox(
height: 30,
),
],
),
),
ListTile(
title: Text(
'Price',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
trailing: IconButton(
onPressed: () {
filters.expandPrice();
},
icon: Icon(
filters.expandedPrice
? Icons.expand_less
: Icons.expand_more,
size: 50,
color: Colors.black)),
),
if (filters.expandedPrice)
Container(
child: Row(children: [
SizedBox(
width: 20,
),
Text(
'bis',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 32),
),
SizedBox(
width: 30,
),
Container(
width: 150,
height: 40,
child: TextField(
keyboardType: TextInputType.number,
onSubmitted: ((value) {
final doubleValue = double.parse(value);
filters.applyFilterUpdate(doubleValue);
}),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15)),
hintText: '\$',
hintStyle: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontSize: 24)),
)),
]),
),
ListTile(
title: Text(
'State',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
trailing: IconButton(
onPressed: () {
filters.expandState();
},
icon: Icon(
filters.expandedState
? Icons.expand_less
: Icons.expand_more,
size: 50,
color: Colors.black)),
),
if (filters.expandedState) FiltersState(),
ListTile(
title: Text(
'Color',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
trailing: IconButton(
onPressed: () {
filters.expandColor();
},
icon: Icon(
filters.expandedColor
? Icons.expand_less
: Icons.expand_more,
size: 50,
color: Colors.black)),
),
if (filters.expandedColor) FilterColor(),
ListTile(
title: Text(
'Brand',
style: Theme.of(context)
.textTheme
.displayMedium!
.copyWith(fontSize: 50),
),
trailing: IconButton(
onPressed: () {
filters.expandBrand();
},
icon: Icon(
filters.expandedBrand
? Icons.expand_less
: Icons.expand_more,
size: 50,
color: Colors.black)),
), //Marken aufzählen List Tile von Zustand verwenden
SizedBox(
height: 30,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); //Filter anwenden
},
child: Text('Apply Filters!')),
]),
),
),
);
}
}
This is my Product Grid Code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shs/providers/products_provider.dart';
import 'product_card.dart';
class ProductGrid extends StatelessWidget {
ScrollController scrollController = ScrollController();
List<String> filtersize;
List<String> filterColor;
String? filterProdCat;
List<String> filterZustand;
double? filterprice;
ProductGrid(
{required this.filterColor,
this.filterProdCat,
required this.filterZustand,
this.filterprice,
required this.filtersize});
Future<void> updateProducts(
{required BuildContext context,
required List<String> filtersize,
required List<String> filterColor,
//required List<String> filterMarke,
String? filterProdCat,
required List<String> filterZustand,
double? filterprice}) async {
await Provider.of<ProductProvider>(
context,
listen: false,
).fetchProducts(
showAll: false,
filtersize: filtersize,
filterColor: filterColor,
//filterMarke: filterMarke,
filterProdCat: filterProdCat,
filterZustand: filterZustand,
filterprice: filterprice);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: updateProducts(
context: context,
filtersize: filtersize,
filterColor: filterColor,
filterZustand: filterZustand),
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Consumer<ProductProvider>(
builder: (ctx, products, _) => GridView.builder(
controller: scrollController,
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: products.items.length,
itemBuilder: ((context, index) => ProductCard(
state: products.items[index].zustand!,
imageUrl: products.items[index].imageUrl,
price: products.items[index].price,
size: products.items[index].size!,
id: products.items[index].id,
prodCat: products.items[index].prodCat,
marke: products.items[index].marke)),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
mainAxisExtent: 400)));
}));
}
}
And here is my dummy product data from firestore
enter image description here
If you need more code or anything else, no problem I will post it!
I'm new to Flutter so my code is a bit messy, I have to admit...
THANK YOU FOR YOUR HELP!
I'm trying to display a result item following the user input in the text field, but I receive all the items. There were some methods I tried, but they didn't work and I encountered some errors.
here is my source code
import 'dart:convert';
import 'package:ebook_flutter_app/constant.dart';
import 'package:ebook_flutter_app/model/image.dart';
import 'package:ebook_flutter_app/model/text_value.dart';
import 'package:ebook_flutter_app/screens/show_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';
import '../widgets/showImage.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
#override
SearchScreenState createState() => SearchScreenState();
}
class SearchScreenState extends State<SearchScreen> {
List textValues = [];
List original = [];
List result = [];
TextEditingController txtQuery = TextEditingController();
List<TextValue> textValueList = [];
List<MyImage> myImageList = [];
List<TextValue> getCatList(List<TextValue> inputList, String query) {
List<TextValue> outputList =
inputList.where((item) => item.title == query).toList();
//textValueList = outputList;
//var myList = outputList;
return outputList;
}
List<MyImage> getImageList(List<MyImage> inputList, String query) {
List<MyImage> outputList =
inputList.where((o) => o.id_num!.toString() == query).toList();
// myImageList = outputList;
return outputList;
}
#override
void initState() {
super.initState();
txtQuery.addListener(() {
if (isNumeric(txtQuery.text) == true) {
loadImage();
searchById(txtQuery.text);
print('I\'m using search option for loading Image.... ');
} else {
loadData();
search(txtQuery.text);
print('I\'m using search option for loading Data....');
}
});
}
void loadData() async {
String jsonStr = await rootBundle.loadString('assets/db/text_value.json');
var json = jsonDecode(jsonStr);
textValues = json;
original = json;
setState(() {});
}
void loadImage() async {
String jsonStr = await rootBundle.loadString('assets/db/image_db.json');
var json = jsonDecode(jsonStr);
textValues = json;
original = json;
print('load Image is running....');
setState(() {});
}
void search(String query) {
if (query.isEmpty) {
textValues = original;
setState(() {});
return;
}
query = query.toLowerCase();
print(query);
//List result = [];
textValues.forEach((element) {
var name = element["name"].toString().toLowerCase();
var description = element["description"].toString().toLowerCase();
if (name.contains(query) || description.contains(query)) {
result.add(element);
// textValueList.add(element);
// print('textValueList is $textValueList');
}
});
textValues = result;
setState(() {});
}
void searchById(String query1) {
if (query1.isEmpty) {
textValues = original;
print('query1 is .....$query1');
setState(() {});
return;
}
print('query1 is $query1');
//List result = [];
textValues.forEach((element) {
var id_num = element["id_num"].toString();
var img_num = element["img_num"].toString();
if (id_num.contains(query1)) {
result.add(element);
// myImageList.add(element);
// print('mYImageList is $myImageList');
print('result is......$result');
}
});
textValues = result;
print('textValues is .....$textValues');
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(2),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: txtQuery,
onChanged: (value) {
setState(() {});
},
textDirection: TextDirection.rtl,
decoration: InputDecoration(
hintText: "جست وجو...",
hintTextDirection: TextDirection.rtl,
hintStyle: TextStyle(
color: Colors.black,
fontSize: 18,
fontFamily: 'iran-sans-ds',
decoration: TextDecoration.none,
fontStyle: FontStyle.italic,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0)),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)),
prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
txtQuery.text = '';
txtQuery.clear();
},
),
),
keyboardType: TextInputType.text,
),
],
),
),
txtQuery.text.isEmpty
? Container()
: Expanded(
child: ListView.builder(
itemCount: textValues.length,
// isNumeric(txtQuery.text) == true
// ? getImageList(myImageList, txtQuery.text).length
// : getCatList(textValueList, txtQuery.text).length,
itemBuilder: (context, index) {
var textVal = textValues[index];
String description = textVal['description'] ??
'we don\'t have description......';
var id_num = textVal['id_num'].toString() ??
'we don\'t have id_num......';
var img_num = textVal['img_num'].toString() ??
'we don\'t have img_num........... ';
print('id_num is ....$id_num'
' img_num is.....$img_num');
return Card(
margin:
const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0)),
color: Colors.blue[50],
child: Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: InkWell(
onTap: (() => pushNewScreen(
context,
screen: isNumeric(id_num) == false
? ShowItem(
name: textVal['name'],
description:
textVal['description'],
)
: ShowImage(
title: id_num,
image: Myasset(img_num),
),
withNavBar:
true, // OPTIONAL VALUE. True by default.
pageTransitionAnimation:
PageTransitionAnimation.slideRight,
)),
child: ExpansionTile(
title: Text(
isNumeric(id_num) == false
? textVal['name']
: id_num,
textDirection: TextDirection.rtl,
style: const TextStyle(
fontSize: 20.0, color: Colors.black54),
),
childrenPadding: const EdgeInsets.only(
bottom: 20.0,
right: 20.0,
left: 20.0,
top: 5.0),
children: [
isNumeric(id_num) == false
? Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
const Text(
'بیشتر',
textDirection:
TextDirection.rtl,
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.blue,
fontWeight:
FontWeight.bold),
),
Text(
'${description.substring(0, 39)} ...',
textDirection:
TextDirection.rtl,
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.black),
),
])
: Image.asset(
Myasset(img_num),
fit: BoxFit.cover,
width: MediaQuery.of(context)
.size
.width *
0.01,
height: MediaQuery.of(context)
.size
.height *
0.01,
),
],
),
),
),
);
}),
)
]),
);
}
}
Widget _listView(text_value) {
return Expanded(
child: ListView.builder(
itemCount: text_value.length,
itemBuilder: (context, index) {
var textVal = text_value[index];
String description =
textVal['description'] ?? 'we don\'t have description......';
var id_num =
textVal['id_num'].toString() ?? 'we don\'t have id_num......';
var img_num = textVal['img_num'].toString() ??
'we don\'t have img_num........... ';
print('id_num is ....$id_num' ' img_num is.....$img_num');
return Card(
margin: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0)),
color: Colors.blue[50],
child: Theme(
data:
Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: InkWell(
onTap: (() => pushNewScreen(
context,
screen: isNumeric(id_num) == false
? ShowItem(
name: textVal['name'],
description: textVal['description'],
)
: ShowImage(
title: id_num,
image: Myasset(img_num),
),
withNavBar: true, // OPTIONAL VALUE. True by default.
pageTransitionAnimation:
PageTransitionAnimation.slideRight,
)),
child: ExpansionTile(
title: Text(
isNumeric(id_num) == false ? textVal['name'] : id_num,
textDirection: TextDirection.rtl,
style:
const TextStyle(fontSize: 20.0, color: Colors.black54),
),
childrenPadding: const EdgeInsets.only(
bottom: 20.0, right: 20.0, left: 20.0, top: 5.0),
children: [
isNumeric(id_num) == false
? Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Text(
'بیشتر',
textDirection: TextDirection.rtl,
textAlign: TextAlign.justify,
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold),
),
Text(
'${description.substring(0, 39)} ...',
textDirection: TextDirection.rtl,
textAlign: TextAlign.justify,
style: TextStyle(color: Colors.black),
),
])
: Image.asset(
Myasset(img_num),
fit: BoxFit.cover,
width: MediaQuery.of(context).size.width * 0.01,
height: MediaQuery.of(context).size.height * 0.01,
),
],
),
),
),
);
}),
);
}
I didn't use getCatList & myImageListmethods because I encountered to error when I was using this methods.
How can I fix it?
Inside addListener you're calling loadData() every single time the user enter a character. Try this:
void initState() {
super.initState();
loadData();
txtQuery.addListener(() {
search(txtQuery.text);
});
}
The second thing you could try is use for in instead of forEach
void search(String query) {
if (query.isEmpty) {
textValues = [];
setState(() {});
return;
}
setState(() {
textValues = [
for (var item in yourDataSource)
if (item['key'].contains(query)) item
];
});
}
And you can use textValues in your list
body: ListView.builder(
itemCount: textValues.length,
You can use autocomplete textfield for your problem
check this plugin :
https://pub.dev/packages/auto_complete_search
You don't have any function for onChanged (TextFormField's argument), let's try this:
First edit your search() function
void search(String query) {
if (query.isEmpty) {
setState(() {
textValues = original;
});
return;
}
setState(() {
textValues = original.where( (element) =>
element['name'].contains(query.toLowerCase()).toString().toLowerCase() ||
element['description'].contains(query.toLowerCase()).toString().toLowerCase()
).toList();
});
}
Second edit onChanged argument
onChanged: (value) => search(value)
I am building a quiz app and I created a custom widget to save me a lot of time as I have a lot of questions for the quiz. Everything works apart from the scoring system. If I create multiple instances of the same widget the score will not be incremented and it will stay on 1. Is there any way I can pass each score of the widgets to a global variable in my main widget so then I can add all the scores? (I'm new to flutter).
Custom Widget
class Questions extends StatefulWidget {
final String imagePath;
final String question;
final String answer1;
final String answer2;
final String answer3;
final String answer4;
final bool iscorrectAnswer1;
final bool iscorrectAnswer2;
final bool iscorrectAnswer3;
final bool iscorrectAnswer4;
int score = 0;
bool questionsAnswered = false;
Questions(
this.imagePath,
this.question,
this.answer1,
this.answer2,
this.answer3,
this.answer4,
this.iscorrectAnswer1,
this.iscorrectAnswer2,
this.iscorrectAnswer3,
this.iscorrectAnswer4,
);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
disableButton() {
setState(() {
widget.questionsAnswered = true;
Quiz().score += widget.score;
});
}
#override
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: 600,
height: 600,
child: Image.asset(widget.imagePath),
),
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(
top: 20,
),
child: Text(
widget.question,
style: TextStyle(
color: Colors.white,
fontSize: 38,
),
),
)),
Padding(
padding: EdgeInsets.only(
top: 40,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color(0xFF304e60),
),
),
child: Text(
widget.answer1,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer1 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
disableButton();
widget.score += 1;
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
print(widget.iscorrectAnswer1);
print(widget.score);
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF565462))),
child: Text(
widget.answer2,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer2 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
)),
Padding(
padding: EdgeInsets.only(
top: 10,
),
child: SizedBox(
width: 500,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Color(0xFF84693b))),
child: Text(
widget.answer3,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
onPressed: widget.questionsAnswered == false
? () {
setState(() {
if (widget.iscorrectAnswer3 == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Correct!'),
),
);
widget.score += 1;
} else {
disableButton();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text('Wrong Answer!'),
));
}
});
}
: null),
),
),
)
],
);
}
}
Main widget where I call this custom widget
class Quiz extends StatefulWidget {
Quiz({Key? key}) : super(key: key);
int score = 0;
#override
_QuizState createState() => _QuizState();
}
class _QuizState extends State<Quiz> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('CyberQuiz'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
Questions(
'images/malware_quiz.jpeg',
'1. What is a malware?',
'Designed to damage computers, servers or any other devices',
"Used to get user's credentials",
"It's used to destroy networks",
'',
true,
false,
false,
false,
),
],
)));
}
}
As you suggest in your question, you could create a global variable and increment/decrease/reset that.
Basic example code:
import 'package:flutter/material.dart';
class Score {
static int score = 0;
}
class ScoreCounter extends StatefulWidget {
const ScoreCounter({Key? key}) : super(key: key);
#override
State<ScoreCounter> createState() => _ScoreCounterState();
}
class _ScoreCounterState extends State<ScoreCounter> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
Score.score++;
});
},
child: Text('increase score'),
),
),
Expanded(child: Text(Score.score.toString()))
],
);
}
}
Another option is to use the Provider package - link here which has an example
Provider Package
Good Morning,
I'm trying to put a Carousel on the home page looking for Firebase data, but for some reason, the first time I load the application it appears the message below:
════════ Exception caught by widgets library ═════════════════════════════════════ ══════════════════
The following _CastError was thrown building DotsIndicator (animation: PageController # 734f9 (one client, offset 0.0), dirty, state: _AnimatedState # 636ca):
Null check operator used on a null value
and the screen looks like this:
After giving a hot reload the error continues to appear, but the image is loaded successfully, any tips of what I can do?
HomeManager:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/models/section.dart';
class HomeManager extends ChangeNotifier{
HomeManager({this.images}){
_loadSections();
images = images ?? [];
}
void addSection(Section section){
_editingSections.add(section);
notifyListeners();
}
final List<dynamic> _sections = [];
List<String> images;
List<dynamic> newImages;
List<dynamic> _editingSections = [];
bool editing = false;
bool loading = false;
int index, totalItems;
final Firestore firestore = Firestore.instance;
Future<void> _loadSections() async{
loading = true;
firestore.collection('home').snapshots().listen((snapshot){
_sections.clear();
for(final DocumentSnapshot document in snapshot.documents){
_sections.add( Section.fromDocument(document));
images = List<String>.from(document.data['images'] as List<dynamic>);
}
});
loading = false;
notifyListeners();
}
List<dynamic> get sections {
if(editing)
return _editingSections;
else
return _sections;
}
void enterEditing({Section section}){
editing = true;
_editingSections = _sections.map((s) => s.clone()).toList();
defineIndex(section: section);
notifyListeners();
}
void saveEditing() async{
bool valid = true;
for(final section in _editingSections){
if(!section.valid()) valid = false;
}
if(!valid) return;
loading = true;
notifyListeners();
for(final section in _editingSections){
await section.save();
}
for(final section in List.from(_sections)){
if(!_editingSections.any((s) => s.id == section.id)){
await section.delete();
}
}
loading = false;
editing = false;
notifyListeners();
}
void discardEditing(){
editing = false;
notifyListeners();
}
void removeSection(Section section){
_editingSections.remove(section);
notifyListeners();
}
void onMoveUp(Section section){
int index = _editingSections.indexOf(section);
if(index != 0) {
_editingSections.remove(section);
_editingSections.insert(index - 1, section);
index = _editingSections.indexOf(section);
}
notifyListeners();
}
HomeManager clone(){
return HomeManager(
images: List.from(images),
);
}
void onMoveDown(Section section){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
if(index < totalItems - 1){
_editingSections.remove(section);
_editingSections.insert(index + 1, section);
index = _editingSections.indexOf(section);
}else{
}
notifyListeners();
}
void defineIndex({Section section}){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
notifyListeners();
}
}
HomeScreen:
import 'package:carousel_pro/carousel_pro.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/commom/custom_drawer.dart';
import 'package:provantagens_app/commom/custom_icons_icons.dart';
import 'package:provantagens_app/models/home_manager.dart';
import 'package:provantagens_app/models/section.dart';
import 'package:provantagens_app/screens/home/components/home_carousel.dart';
import 'package:provantagens_app/screens/home/components/menu_icon_tile.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
// ignore: must_be_immutable
class HomeScreen extends StatelessWidget {
HomeManager homeManager;
Section section;
List<Widget> get children => null;
String videoUrl = 'https://www.youtube.com/watch?v=VFnDo3JUzjs';
int index;
var _tapPosition;
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: const [
Colors.white,
Colors.white,
], begin: Alignment.topCenter, end: Alignment.bottomCenter)),
child: Scaffold(
backgroundColor: Colors.transparent,
drawer: CustomDrawer(),
appBar: AppBar(
backgroundColor: Colors.transparent,
iconTheme: IconThemeData(color: Colors.black),
title: Text('Página inicial', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8))),
centerTitle: true,
actions: <Widget>[
Divider(),
],
),
body: Consumer<HomeManager>(
builder: (_, homeManager, __){
return ListView(children: <Widget>[
AspectRatio(
aspectRatio: 1,
child:HomeCarousel(homeManager),
),
Column(
children: <Widget>[
Container(
height: 50,
),
Divider(
color: Colors.black,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(left:12.0),
child: MenuIconTile(title: 'Parceiros', iconData: Icons.apartment, page: 1,),
),
Padding(
padding: const EdgeInsets.only(left:7.0),
child: MenuIconTile(title: 'Beneficios', iconData: Icons.card_giftcard, page: 2,),
),
Padding(
padding: const EdgeInsets.only(right:3.0),
child: MenuIconTile(title: 'Suporte', iconData: Icons.help_outline, page: 6,),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MenuIconTile(iconData: Icons.assignment,
title: 'Dados pessoais',
page: 3)
,
MenuIconTile(iconData: Icons.credit_card_outlined,
title: 'Meu cartão',
page: 4)
,
MenuIconTile(iconData: Icons.account_balance_wallet_outlined,
title: 'Pagamento',
page: 5,)
,
],
),
Divider(
color: Colors.black,
),
Container(
height: 50,
),
Consumer<HomeManager>(
builder: (_, sec, __){
return RaisedButton(
child: Text('Teste'),
onPressed: (){
Navigator.of(context)
.pushReplacementNamed('/teste',
arguments: sec);
},
);
},
),
Text('Saiba onde usar o seu', style: TextStyle(color: Colors.black, fontSize: 20),),
Text('Cartão Pró Vantagens', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8), fontSize: 30),),
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_80b5f6510f5841c19046f1ed5bca71e4~mv2.png/v1/fill/w_745,h_595,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es.webp',
fit: BoxFit.fill,
),
),
Divider(),
Container(
height: 150,
child: Row(
children: [
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_486dd638987b4ef48d12a4bafee20e80~mv2.png/v1/fill/w_684,h_547,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es_2.webp',
fit: BoxFit.fill,
),
),
Padding(
padding: const EdgeInsets.only(left:20.0),
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: 'Adquira já o seu',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: '\n\CARTÃO PRÓ VANTAGENS',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
),
],
),
),
Divider(),
tableBeneficios(),
Divider(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'O cartão Pró-Vantagens é sediado na cidade de Hortolândia/SP e já está no mercado há mais de 3 anos. Somos um time de profissionais apaixonados por gestão de benefícios e empenhados em gerar o máximo de valor para os conveniados.'),
FlatButton(
onPressed: () {
launch(
'https://www.youtube.com/watch?v=VFnDo3JUzjs');
},
child: Text('SAIBA MAIS')),
],
),
),
Container(
color: Color.fromARGB(255, 105, 190, 90),
child: Column(
children: <Widget>[
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'© 2020 todos os direitos reservados a Cartão Pró Vantagens.',
style: TextStyle(fontSize: 10),
),
)
],
),
Divider(),
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Rua Luís Camilo de Camargo, 175 -\n\Centro, Hortolândia (piso superior)',
style: TextStyle(fontSize: 10),
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.facebook),
color: Colors.black,
onPressed: () {
launch(
'https://www.facebook.com/provantagens/');
},
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.instagram),
color: Colors.black,
onPressed: () {
launch(
'https://www.instagram.com/cartaoprovantagens/');
},
),
),
],
),
],
),
)
],
),
]);
},
)
),
);
}
tableBeneficios() {
return Table(
defaultColumnWidth: FlexColumnWidth(120.0),
border: TableBorder(
horizontalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
verticalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
),
children: [
_criarTituloTable(",Plus, Premium"),
_criarLinhaTable("Seguro de vida\n\(Morte Acidental),X,X"),
_criarLinhaTable("Seguro de Vida\n\(Qualquer natureza),,X"),
_criarLinhaTable("Invalidez Total e Parcial,X,X"),
_criarLinhaTable("Assistência Residencial,X,X"),
_criarLinhaTable("Assistência Funeral,X,X"),
_criarLinhaTable("Assistência Pet,X,X"),
_criarLinhaTable("Assistência Natalidade,X,X"),
_criarLinhaTable("Assistência Eletroassist,X,X"),
_criarLinhaTable("Assistência Alimentação,X,X"),
_criarLinhaTable("Descontos em Parceiros,X,X"),
],
);
}
_criarLinhaTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name != "X" ? '' : 'X',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name != 'X' ? name : '',
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
_criarTituloTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name == "" ? '' : 'Plano ',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
I forked the library to create a custom carousel for my company's project, and since we updated flutter to 2.x we had the same problem.
To fix this just update boolean expressions like
if(carouselState.pageController.position.minScrollExtent == null ||
carouselState.pageController.position.maxScrollExtent == null){ ... }
to
if(!carouselState.pageController.position.hasContentDimensions){ ... }
Here is flutter's github reference.
This worked for me
So I edited scrollposition.dart package
from line 133
#override
//double get minScrollExtent => _minScrollExtent!;
// double? _minScrollExtent;
double get minScrollExtent {
if (_minScrollExtent == null) {
_minScrollExtent = 0.0;
}
return double.parse(_minScrollExtent.toString());
}
double? _minScrollExtent;
#override
// double get maxScrollExtent => _maxScrollExtent!;
// double? _maxScrollExtent;
double get maxScrollExtent {
if (_maxScrollExtent == null) {
_maxScrollExtent = 0.0;
}
return double.parse(_maxScrollExtent.toString());
}
double? _maxScrollExtent;
Just upgrade to ^3.0.0 Check here https://pub.dev/packages/carousel_slider
I faced the same issue.
This is how I solved it
class PlansPage extends StatefulWidget {
const PlansPage({Key? key}) : super(key: key);
#override
State<PlansPage> createState() => _PlansPageState();
}
class _PlansPageState extends State<PlansPage> {
int _currentPage = 1;
late CarouselController carouselController;
#override
void initState() {
super.initState();
carouselController = CarouselController();
}
}
Then put initialization the carouselController inside the initState method I was able to use the methods jumpToPage(_currentPage ) and animateToPage(_currentPage) etc.
I use animateToPage inside GestureDetector in onTap.
onTap: () {
setState(() {
_currentPage = pageIndex;
});
carouselController.animateToPage(_currentPage);
},
I apologize in advance if this is inappropriate.
I solved the similar problem as follows. You can take advantage of the Boolean variable. I hope, help you.
child: !loading ? HomeCarousel(homeManager) : Center(child:ProgressIndicator()),
or
child: isLoading ? HomeCarousel(homeManager) : SplashScreen(),
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Loading...')
),
);
}
}
I'm trying to implement a button animation when on clicked it shrinks and shows the circular progress indicator (while loading) then expands and shows the result of the executed operation (in this case it is login).
The code idea came from this link. The design idea came from this link. Now I implemented this before and it worked exactly as it was supposed to. However when implementing it again here, on button pressed -> the person logs in successfully and the button changes color as per design an all. The only problem is that the button animation does not happen. I tried printing the values of the _loginButtonWidth and can actually see it decreasing and increasing as per design, but visually the width stays the same.
Code:
import 'package:flutter/material.dart';
import 'package:garuda_academy_app/Login/Authentication.dart';
import 'package:garuda_academy_app/Tools/FixedColors.dart';
import 'dart:async';
class LoginPage extends StatefulWidget {
LoginPage({this.auth, this.onLoggedIn});
#override
_LoginPageState createState() => _LoginPageState();
final BaseAuth auth;
final VoidCallback onLoggedIn;
}
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
// for device type
bool _isIos;
// text form field
String _userEmail = "";
String _userPassword = "";
final _formKey = GlobalKey<FormState>();
// for login button
int _loginButtonState = 0;
double _loginButtonWidth = double.maxFinite;
Color _loginButtonColor = primaryColor;
Color _loginButtonOutlineColor = primaryColor;
Color _loginButtonTextColor = secondaryColor;
GlobalKey _loginButtonKey = GlobalKey();
Animation _loginButtonAnimation;
AnimationController _loginButtonController;
Widget _loginButton() {
if (_loginButtonState == 0) {
return Text(
"Log In",
style: TextStyle(
color: _loginButtonTextColor,
fontSize: 20,
),
);
} else if (_loginButtonState == 1) {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(secondaryColor),
);
} else if (_loginButtonState == 2) {
return Icon(
Icons.check,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 3) {
return Icon(
Icons.close,
color: _loginButtonTextColor,
);
} else if (_loginButtonState == 4) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.check,
color: _loginButtonTextColor,
),
Icon(
Icons.check,
color: transparent,
),
Text(
"Successful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
} else if (_loginButtonState == 5) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.close,
color: _loginButtonTextColor,
),
Icon(
Icons.close,
color: transparent,
),
Text(
"Unsuccessful",
style: TextStyle(
color: _loginButtonTextColor,
),
),
],
);
}
}
bool _validateLoginAndSave() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return true;
}
return false;
}
_animateLoginButton() async {
String userId = "";
String errorMsg = "";
setState(() {
_loginButtonState = 1;
});
// animation
double initialWidth = _loginButtonKey.currentContext.size.width;
_loginButtonController =
AnimationController(duration: Duration(milliseconds: 300), vsync: this)
..addStatusListener((AnimationStatus status) async {
if (status == AnimationStatus.completed) {
// firebase signin
try {
userId = await widget.auth.signIn(_userEmail, _userPassword);
} catch (e) {
setState(() {
errorMsg = _isIos ? e.details : e.message;
print(errorMsg);
});
}
// loading timer
Timer(Duration(seconds: 1), () {
// set login state
_loginButtonState =
(userId.length > 0 && userId != null) ? 2 : 3;
// change colors
if (_loginButtonState == 2) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = successfulColor;
_loginButtonTextColor = successfulColor;
} else if (_loginButtonState == 3) {
_loginButtonColor = secondaryColor;
_loginButtonOutlineColor = unsuccessfulColor;
_loginButtonTextColor = unsuccessfulColor;
}
_loginButtonController.reverse();
});
} else if (status == AnimationStatus.dismissed) {
if (_loginButtonState == 2) {
_loginButtonState = 4;
} else if (_loginButtonState == 3) {
_loginButtonState = 5;
}
// minimal time before it is done
Timer(Duration(seconds: 1), () {
setState(() {
if (_loginButtonState == 4) widget.onLoggedIn();
// reset state
_loginButtonState = 0;
// reset colors
_loginButtonColor = primaryColor;
_loginButtonOutlineColor = primaryColor;
_loginButtonTextColor = secondaryColor;
});
});
}
});
_loginButtonAnimation =
Tween(begin: 0.0, end: 1.0).animate(_loginButtonController)
..addListener(() {
setState(() {
_loginButtonWidth = initialWidth -
((initialWidth - 80.0) * _loginButtonAnimation.value);
});
print("initial: " + initialWidth.toString());
print("current: " + _loginButtonWidth.toString());
});
_loginButtonController.forward();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
_isIos = Theme.of(context).platform == TargetPlatform.iOS;
return Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
child: Center(
child: Theme(
data: ThemeData(primaryColor: primaryColor),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.all(40),
child: Text(
"Log in to continue",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Email Address",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Email cannot be empty" : null,
onSaved: (value) => _userEmail = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 20, left: 40, right: 40),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
obscureText: true,
style: TextStyle(
fontSize: 20,
),
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(fontSize: 20),
),
validator: (value) =>
value.isEmpty ? "Password cannot be empty" : null,
onSaved: (value) => _userPassword = value,
),
),
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Container(
height: 60,
width: _loginButtonWidth,
child: PhysicalModel(
color: transparent,
borderRadius: BorderRadius.circular(10.0),
child: RaisedButton(
elevation: 8.0,
color: _loginButtonColor,
key: _loginButtonKey,
shape: OutlineInputBorder(
borderSide: BorderSide(
color: _loginButtonOutlineColor,
),
borderRadius: BorderRadius.circular(10.0),
),
child: _loginButton(),
onPressed: () {
setState(() {
if (_loginButtonState == 0 &&
_validateLoginAndSave()) {
_animateLoginButton();
}
});
},
),
),
),
),
],
),
),
),
),
),
);
}
}
Button width stays the same when it is supposed to shrink:
Easy fix, add a Center or Align widget as a parent of your Container button.
Padding(
padding: EdgeInsets.only(bottom: 50, left: 40, right: 40),
child: Center(
child: Container(
height: 60,
width: _loginButtonWidth,
To get more info check Layout Behavior