Related
I a beginner in Flutter development and I was trying to replicate an animated login screen from this LinkedIn post. I am using Rive animations to make all the states required for the screen possible. A base rive file was available for the screen and after some tweaks, I was able to get the required states.
After implementing the animations in the screen along with two text fields, I noticed an issue with the animations. Whenever I click on the TextField, the soft keyboard pops up. The required animation runs for a short duration then resets to the idle animation. After digging through stack overflow and google and printing debug messages, I noticed that the whole widget rebuilds itself whenever soft keyboard pops up.
Here's the code for my screen -
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
#override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
StateMachineController? _controller;
SMIInput<bool>? isChecking;
SMIInput<bool>? trigSuccess;
SMIInput<bool>? trigFail;
SMIInput<bool>? isHandsUp;
SMIInput<double>? numLook;
FocusNode emailFocusNode = FocusNode();
TextEditingController emailController = TextEditingController();
FocusNode passwordFocusNode = FocusNode();
TextEditingController passController = TextEditingController();
#override
void initState() {
super.initState();
emailFocusNode.addListener(emailFocus);
passwordFocusNode.addListener(passFocus);
}
#override
void dispose() {
super.dispose();
emailFocusNode.removeListener(emailFocus);
passwordFocusNode.removeListener(passFocus);
}
void emailFocus() {
isChecking?.change(emailFocusNode.hasFocus);
}
void passFocus() {
isHandsUp?.change(passwordFocusNode.hasFocus);
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Color(0xffeef2f3),
// Colors.lightBlueAccent.withBlue(255).withGreen(242).withRed(238),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: size.width,
height: size.height,
child: Center(
child: Container(
height: 600,
width: 320,
decoration: BoxDecoration(
color: Color(0xFFffffff),
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 5,
),
],
),
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 25,
horizontal: 25,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CircleAvatar(
backgroundColor: Colors.black87,
radius: 90,
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 88,
child: ClipOval(
child: RiveAnimation.asset(
'assets/login_screen_character.riv',
stateMachines: ["Login Machine"],
onInit: (Artboard artboard) {
_controller =
StateMachineController.fromArtboard(
artboard, "Login Machine");
if (_controller == null) return;
artboard.addController(_controller!);
isChecking =
_controller?.findInput("isChecking");
trigSuccess =
_controller?.findInput("trigSuccess");
trigFail = _controller?.findInput("trigFail");
isHandsUp = _controller?.findInput("isHandsUp");
numLook = _controller?.findInput("numLook");
},
),
),
),
),
SizedBox(
height: 32,
),
Text(
"Email",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xff32779d),
),
),
SizedBox(
height: 4,
),
Container(
decoration: BoxDecoration(
color: Color(0x321fb0d2),
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Color(0xff1fb0d2),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
top: 8,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"email#example.com",
style: TextStyle(
fontSize: 12,
color: Color(0xff32779d),
),
),
TextField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter email",
),
focusNode: emailFocusNode,
controller: emailController,
onChanged: (value) {
if (isChecking?.value == false)
isChecking?.change(true);
numLook?.change(value.length.toDouble() * 3);
},
),
],
),
),
),
SizedBox(
height: 32,
),
Text(
"Password",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xff32779d),
),
),
SizedBox(
height: 2,
),
Container(
decoration: BoxDecoration(
color: Color(0x321fb0d2),
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Color(0xff1fb0d2),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
top: 4,
bottom: 4,
),
child: TextField(
onChanged: (value) {
if (isHandsUp?.value == false)
isHandsUp?.change(true);
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter password",
),
focusNode: passwordFocusNode,
controller: passController,
),
),
),
SizedBox(
height: 32,
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color(0xff1fb0d2),
),
),
onPressed: () {
if (isHandsUp?.value == true)
isHandsUp?.change(false);
Future.delayed(Duration(milliseconds: 1200), () {
if (passController.text == "thisisatest")
trigSuccess?.change(true);
else
trigFail?.change(true);
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
"Log In",
style: TextStyle(
color: Colors.white,
),
),
),
)
],
),
),
),
),
),
),
),
);
}
}
The code is a bit too long, please excuse me for that I haven't really refactored the code.
I've tried using the resizeToAvoidBottomInset: false but animation still resets to the idle state. I've even tried adding the android:windowSoftInputMode="adjustNothing" inside the <activity...> tag but still no result.
I would really like to know if there's any workaround in my code to make the animation states run properly.
This is my output
all these functions are works excellent. But I tried to add sharedPrefence to these dropdowns, select birthday and to the checkbox. But both dropdowns work perfectly, When I select something in the dropdown and click the next button and close the app and reopen again then shows the words that are selected at the last. But after that, I tried to add sharedPrefence to datePicker and thin "Are you agree " check box but then shows some errors. I have no idea how to solve that.
EX:- In this, I used a date picker, Agree checkbox and in there shows "Birthday" word as an initial date and check box is unselecting, when the click selects the button then shows the dates and pick date and can select check box then value is true, That works perfectly. But I wanna add shared Preferences to this When added that, after the selected word and close the app and reopened again then should show the date and check box value true that is selected at the last.
my full code.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class FamilyDetailsScreen extends StatefulWidget {
#override
State<FamilyDetailsScreen> createState() => _FamilyDetailsScreenState();
}
class _FamilyDetailsScreenState extends State<FamilyDetailsScreen> {
// 1st dropdown button
#override
void initState() {
super.initState();
dropdownValueMembers = items.first;
dropdownValueNumber = number.first;
dropdownValueBirthday = birthday.first;
dropdownValueBirthday = Agree.first;
checkValueMembers();
checkValueNumber();
checkValueBirthday();
checkValueAgree();
}
String? dropdownValueMembers;
// List of items in our dropdown menu
List<String> items = [
'howmany',
'one',
'two',
'three ',
'four',
'5 or more'
];
checkValueMembers() {
_getData();
}
_saveData(String dropdownValueMembersShared) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("data", dropdownValueMembersShared);
}
_getData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
dropdownValueMembers = sharedPreferences.getString("data") ?? items.first;
setState(() {});
}
// 2nd dropdown button
// data which child
String? dropdownValueNumber;
// // List of items in our dropdown menu
List<String> number = ['which', '1 st', '2 nd', '3 rd ', '4 th ', '5 th'];
//IF "dropdownValueMembers" is empty pass "which" word as a initial value if al ready selected then pass the shared preference value
checkValueNumber() {
_getDataNumber();
}
_saveDataNumbers(String dropdownValueNumberShared) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("data2", dropdownValueNumberShared);
}
_getDataNumber() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
dropdownValueNumber = sharedPreferences.getString("data2") ?? number.first;
setState(() {});
}
//date picker
DateTime? selectedDate;
DateTime now = new DateTime.now();
void showDatePicker() {
DateTime mindate = DateTime(now.year - 2, now.month, now.day - 29);
DateTime maxdate = DateTime(now.year - 1, now.month, now.day);
showCupertinoModalPopup(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context).copyWith().size.height * 0.25,
color: Colors.white,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: mindate,
onDateTimeChanged: (value) {
if (value != null && value != selectedDate) {
setState(() {
selectedDate = value;
});
}
},
maximumDate: maxdate,
minimumDate: mindate,
),
);
});
}
String? dropdownValueBirthday;
List<String> birthday = [
'Birthday',
];
//sharedPreferences birthday
checkValueBirthday() {
_getDataBirthday();
}
_saveDataBirthday(String dropdownValueBirthdayShared) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("dataBirthday", dropdownValueBirthdayShared);
}
_getDataBirthday() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
dropdownValueBirthday =
sharedPreferences.getString("dataBirthday") ?? birthday.first;
setState(() {});
}
//////////////////////////////////
// are you agree button
//boolean value (are you agree)
bool isChecked = false;
checkValueAgree() {
_getDataAgree();
}
_saveDataAgree(String dropdownValueAgreeShared) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setBool("Agree", dropdownValueAgreeShared);
}
_getDataAgree() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
dropdownValueNumber = sharedPreferences.getBool("Agree") ?? isChecked.first;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Column(
children: <Widget>[
const Text(
'family details',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontSize: 18.00,
fontWeight: FontWeight.w700,
),
),
const SizedBox(
height: 30,
),
Padding(
padding: const EdgeInsets.only(top: 10, left: 15),
child: Row(
children: <Widget>[
const Icon(
Icons.brightness_1,
color: Colors.black,
size: 10.0,
),
const Padding(
padding: EdgeInsets.only(left: 13),
child: Text(
"Number of children",
style:
TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
),
],
),
),
const SizedBox(
height: 25,
),
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 2),
child: Container(
height: 25,
decoration: BoxDecoration(
boxShadow: const <BoxShadow>[
//apply shadow on Dropdown button
BoxShadow(
color: Color.fromRGBO(
0, 0, 0, 0.37), //shadow for button
blurRadius: 5) //blur radius of shadow
],
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: DropdownButton(
underline: Container(),
borderRadius: BorderRadius.circular(20),
// Initial Value
value: dropdownValueMembers,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
// Array list of items
items: items.map((String data) {
return DropdownMenuItem(
value: data,
child: SizedBox(
height: 15,
width: 120.0, // for example
child: Text(data,
style: const TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w700),
textAlign: TextAlign.center),
),
);
}).toList(),
// After selecting the desired option,it will
// change button value to selected value
onChanged: (String? newValue) {
setState(
() {
dropdownValueMembers = newValue!;
},
);
},
),
),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text('Which child'),
),
Padding(
padding: const EdgeInsets.only(left: 100, right: 0, top: 20),
child: Container(
height: 30,
decoration: BoxDecoration(
boxShadow: const <BoxShadow>[
//apply shadow on Dropdown button
BoxShadow(
color: Color.fromRGBO(
0, 0, 0, 0.37), //shadow for button
blurRadius: 5) //blur radius of shadow
],
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: DropdownButton(
underline: Container(),
borderRadius: BorderRadius.circular(20),
// Initial Value
value: dropdownValueNumber,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
// Array list of items
items: number.map((String number) {
return DropdownMenuItem(
value: number,
child: SizedBox(
height: 17,
width: 120.0, // for example
child: Text(number,
style: const TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w700),
textAlign: TextAlign.center),
),
);
}).toList(),
// After selecting the desired option,it will
// change button value to selected value
onChanged: (String? newNumber) {
setState(
() {
dropdownValueNumber = newNumber!;
},
);
},
),
),
),
],
),
const SizedBox(
height: 60,
),
Padding(
padding: const EdgeInsets.only(left: 15),
child: Row(
children: <Widget>[
const Icon(
Icons.brightness_1,
color: Colors.black,
size: 10,
),
const Padding(
padding: EdgeInsets.only(left: 15.0),
child: Text("birthday",
style: TextStyle(
fontSize: 16.0,
)),
),
Padding(
padding: const EdgeInsets.only(left: 25.0),
child: SizedBox(
width: 110.0,
height: 25.0,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Center(
child: Text(
selectedDate == null
? (dropdownValueBirthday ?? birthday.first)
: '${selectedDate?.year}/${selectedDate?.month}/${selectedDate?.day} ',
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w500),
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: SizedBox(
width: 88.0,
height: 25.0,
child: MaterialButton(
onPressed: showDatePicker,
shape: const StadiumBorder(),
color: Colors.blue,
child: const Text(
'select',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Row(
children: <Widget>[
const Icon(
Icons.brightness_1,
color: Colors.black,
size: 10,
),
const Padding(
padding: EdgeInsets.only(left: 15.0),
child: Text(
"Are you agree",
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
),
const Padding(
padding: EdgeInsets.only(left: 30),
child: Text(
'yes',
style:
TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
Checkbox(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
side: MaterialStateBorderSide.resolveWith(
(states) =>
BorderSide(width: 3.0, color: Colors.blueAccent),
),
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 0.0, top: 150),
child: SizedBox(
width: 160.0,
height: 35.0,
child: ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: const BorderSide(
color: Colors.blueAccent,
),
),
),
),
onPressed: () {
//do null check 1st
_saveData(dropdownValueMembers!);
_saveDataNumbers(dropdownValueNumber!);
_saveDataBirthday(dropdownValueBirthday!);
_saveDataAgree(isChecked!);
},
child: const Text('next')),
),
),
],
),
),
);
}
}
How to solve and how to add sharedPreference to this datepicker and to checkbox
Lot's of ways to go about it. Simplist way in your case, get rid of all the setStates in all your methods except the last one. You're layering all those setStates and they're calling while other things are still awaiting.
I suggest awaiting each method before calling any setState. You can't await in the initState, but in the first method, call the each of the subsequent ones with awaits from there. So just call checkValueMembers(); (or create a new one getAllValues or whatever).
Within getAllValues, await all the other methods.
Then you can be sure that everything is done, and finally, call setState this way:
WidgetsBinding.instance.addPostFrameCallback((_) {
setState((){});
});
It ensures you don't call setState until the entire widget tree is done.
Entire thing could look like this: remember to get rid of all the other setStates
Future checkValueMembers() async {
await _getData();
await _getDataNumber();
await _getDataBirthday();
await _getDataAgree();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState((){});
});
}
To keep things short:
I have multiple products, where you can increment and decrement their value(qnty) inside the cart and after Submitting, a receipt is generated based on the cart Items.
So the Problem is whenever I try to slide the submit, the qnty of the first product I added to cart is assigned to every product, Like
• Apple: 1
• Mango: 2
• Orange: 6
Above is how it should be like
• Apple: 6
• Mango: 6
• Orange: 6
This is the result I am getting, Note: The Result is from new to old
Secondary issue is that whenever I try to write any value inside the textfield and click submit, the value still doesn't get updated!
The Code consists of 2 files:
Parent File
import 'package:ambica_medico/component/result/productcart.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:whatsapp_unilink/whatsapp_unilink.dart';
import '../../component/buttons/c_button.dart';
import '../../constant.dart';
final FirebaseAuth auth = FirebaseAuth.instance;
Stream<QuerySnapshot> getData() => FirebaseFirestore.instance
.collection('Users')
.doc(auth.currentUser?.uid)
.collection('Carts')
.snapshots();
class Cart extends StatefulWidget {
const Cart({Key? key}) : super(key: key);
#override
State<Cart> createState() => _CartState();
}
class _CartState extends State<Cart> {
String _text = '';
callback(newAbc) {
pk = newAbc;
} //Step 5: Callback ready to be passed to the the Procart.
String? product;
String qnty = '';
String? mail;
String? name;
String? address;
String? dln;
String? gst;
late final _getData = getData();
DateTime now = DateTime.now();
Map<String, String> pk = {};
#override
Widget build(BuildContext context) {
return StreamBuilder<dynamic>(
stream: _getData,
builder: (context, snapshot) {
final tilesList = <Widget>[];
if (snapshot.hasData) {
snapshot.data.docs.forEach((value) {
qnty = value.data()['SIB'].toString();
pk = {value.id: qnty}; //Step4: A map which holds every product id and qnty
final productTile = Procart(
pname: value.data()['Product'],
subtitle: value.data()['MRP'],
keyo: value.id,
controller: qnty, sib: value.data()['OSIB'], tis: value.data()['TIS'], callback: callback, //Callback passed!
);
if (_text.isEmpty) {
tilesList.add(productTile);
} else {
if (value
.data()['Product']
.toUpperCase()
.contains(_text.toUpperCase())) {
tilesList.add(productTile);
}
}
// print(pk.values); //Returns 5,1
});
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: Padding(
padding: const EdgeInsets.only(left: 20.0, top: 10),
child: IconButton(
icon: const Icon(
Icons.arrow_back_ios_new_rounded,
color: Colors.black,
size: 20,
),
onPressed: () {
Navigator.pop(context);
},
),
),
backgroundColor: const Color(0xFFf5f3f7),
elevation: 0,
),
body: GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Container(
decoration: kImageBackground.copyWith(),
height: MediaQuery.of(context).size.height,
child: Stack(children: [
Column(
children: [
SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(
left: 24.0, right: 24.0, top: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
'The Cart',
style: TextStyle(
fontSize: 40,
fontFamily: 'ProductSans',
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Padding(
padding:
const EdgeInsets.only(top: 50, bottom: 50),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
boxShadow: const [
BoxShadow(
color: Color(0x261B1B1A),
blurRadius: 50.0,
spreadRadius: 0,
offset: Offset(0.0, 30.0),
),
],
),
height: 70,
child: Center(
child: TextField(
onChanged: (value) {
setState(() {
_text = value;
});
},
keyboardType: TextInputType.text,
decoration: kDecorS.copyWith(
hintText: 'Search Products',
),
style: const TextStyle(
fontFamily: 'ProductSans',
fontSize: 18,
fontWeight: FontWeight.w400,
color: Color(0xff0f1511),
),
),
),
),
),
],
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.55,
child: ListView(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
children: tilesList,
),
),
],
),
Positioned(
bottom: 0,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(42.0),
topRight: Radius.circular(42.0),
),
color: Colors.white,
),
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Column(
children: [
const Padding(
padding:
EdgeInsets.only(bottom: 10.0),
child: Center(
child: Text(
'Check and then click below to',
style: TextStyle(
fontSize: 14,
fontFamily: 'ProductSans',
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
),
Cbutton(
text: 'Send Order',
onPressed: () async {
String message = "";
DateTime date = DateTime(
now.year, now.month, now.day);
await FirebaseFirestore.instance
.collection('Users')
.doc(auth.currentUser?.uid)
.get()
.then((value) => {
name = value.data()!['name'],
address =
value.data()!['address'],
dln = value.data()!['dln'],
gst = value.data()!['gst'],
});
await snapshot.data.docs
.forEach((value) async {
product = value.data()['Product'];
message += '- $product = ${pk.values} \n';
});
final Email email = Email(
body:
"From:- \n\nName: $name\n\nAddress: $address\n\nDrug License No:- $dln\n\nGST No:- $gst\n\nDate:- $date \n\nDear sir,\nPlease dispatch my following order earliest possible through\n $message \n\nThanks & Regards,\n$name",
subject: 'Order Detail',
recipients: ['calagency03#gmail.com'],
isHTML: false,
);
final link = WhatsAppUnilink(
phoneNumber: '+91 2313210000',
text:
"From:- \n\nName: $name\n\nAddress: $address\n\nDrug License No:- $dln\n\nGST No:- $gst\n\nDate:- $date \n\nDear sir,\nPlease dispatch my following order earliest possible through\n $message \n\nThanks & Regards,\n$name",
);
await FlutterEmailSender.send(email);
final url = Uri.parse('$link');
await launchUrl(url, mode: LaunchMode.externalApplication,);
},
icon: const Icon(
Icons.send_rounded,
color: Colors.black,
size: 20,
),
color: const Color(0xff0f1511),
),
],
),
),
),
),
),
)
]),
),
),
),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
});
}
}
The main logic of printing the receipt for a product is:
await snapshot.data.docs
.forEach((value) async {
product = value.data()['Product'];
message += '- $product = ${pk.values} \n';
});
Child File
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class Procart extends StatefulWidget {
final String pname;
final String subtitle;
final String keyo; // Step1: Product id
final String sib;
final String tis;
final String controller; // Step2: Initial Value/ Qnty
final Function(Map) callback; // Step3: Callback to parents widget in which it passes updated qnty
const Procart(
{Key? key,
required this.pname,
required this.subtitle,
required this.keyo, required this.controller, required this.sib, required this.tis, required this.callback})
: super(key: key);
#override
State<Procart> createState() => _ProcartState();
}
class _ProcartState extends State<Procart> {
final FirebaseAuth auth = FirebaseAuth.instance;
late TextEditingController controller = TextEditingController(text: widget.controller);
Map<String, String> lk = {};
sub() {
setState(() {
controller.text =
(int.parse(controller.text) - 1).toString();
});
}
add() {
setState(() {
controller.text =
(int.parse(controller.text) + 1).toString();
});
}
// #override
// void didUpdateWidget(covariant Procart oldWidget) {
// // TODO: implement didUpdateWidget
// super.didUpdateWidget(oldWidget);
//
//
// }
#override
Widget build(BuildContext context) {
lk = { widget.keyo : controller.text }; // This map is used to store updated value
widget.callback(lk); // send updated value back to parent class but the value still remains 2,2,2 instead of 1,7,2
print(lk.values);
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Slidable(
key: Key(widget.keyo),
endActionPane: ActionPane(motion: const ScrollMotion(), children: [
SlidableAction(
// An action can be bigger than the others.
onPressed: (value) {
FirebaseFirestore.instance
.collection('Users')
.doc(auth.currentUser?.uid)
.collection('Carts')
.doc(widget.keyo)
.delete();
},
backgroundColor: const Color(0xFFD16464),
foregroundColor: Colors.white,
icon: Icons.clear_rounded,
),
]),
child: Padding(
padding: const EdgeInsets.only(left: 28.0, right: 28.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10)
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.pname,
style: const TextStyle(
fontFamily: 'Satoshi',
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xff0f1511),
),
),
Text(
'${widget.subtitle} | ${widget.sib} x ${widget.tis}',
style: const TextStyle(
fontFamily: 'Satoshi',
fontSize: 12,
fontWeight: FontWeight.normal,
color: Color(0xff0f1511),
),
),
],
),
),
Row(
children: [
CircleAvatar(
radius: 16,
backgroundColor: const Color(0xff1b1b1b),
child: IconButton(
iconSize: 13,
icon: const Icon(
Icons.remove,
color: Colors.white,
),
onPressed: () {
if (int.parse(controller.text) >
int.parse(widget.sib)) {
sub();
}
},
),
),
SizedBox(
width: 80,
child: TextFormField(
textAlign: TextAlign.center,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.only(left: 6, right: 6),
),
controller: controller,
onEditingComplete: () {
controller;
},
keyboardType: TextInputType.number,
style: const TextStyle(
fontFamily: 'Satoshi',
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Color(0xff0f1511),
),
),
),
CircleAvatar(
radius: 16,
backgroundColor: const Color(0x33bababa),
child: IconButton(
iconSize: 13,
icon: const Icon(
Icons.add,
color: Color(0xff1b1b1b),
),
onPressed: () {
add();
},
),
),
],
),
]),
),
),
))),
);
}
}
For more info I have attached Screenshots below:
A Huge Thank you to anyone who helps me in solving this!
Well, I solved this issue by following the steps below:
I have some trouble with hiding and showing widget. So, I have an OTP verification page to verify the code that sent to the user. In that page, when a user click a text button to resend otp code, the button will be hidden and replaced by countdown timer.
showCountdown ?
Center(
child: CountdownTimer(
textStyle: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
endTime: endTime,
onEnd: () {
setState(() {
showCountdown = false;
});
},
),
)
:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Tidak Menerima Kode Verifikasi? ",
style: TextStyle(color: Colors.black54, fontSize: 15),
),
TextButton(
onPressed: () {
// userService.resendOtp(widget.phoneNumber.toString()).then((value) => snackBar(value));
setState(() {
showCountdown = true;
clicked++;
});
},
child: Text(
"Kirim Ulang",
style: TextStyle(
color: Constant.color,
fontWeight: FontWeight.bold,
fontSize: 16),
)
)
],
),
First click is okay, but when clicked for second time, it give this error
The following assertion was thrown building NotificationListener<KeepAliveNotification>:
setState() or markNeedsBuild() called during build.
It shows that the error maybe on this part
onEnd: () {
setState(() {
showCountdown = false;
});
},
Full Code for build method
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/Background.png"),
alignment: Alignment.center,
fit: BoxFit.cover
)
),
child: ListView(
children: <Widget>[
// SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'Verifikasi Nomor HP',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
textAlign: TextAlign.center,
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 30.0, vertical: 8),
child: RichText(
text: TextSpan(
text: "Kode Dikirim Ke Nomor ",
children: [
TextSpan(
text: "${widget.phoneNumber}",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 15)),
],
style: TextStyle(color: Colors.black54, fontSize: 15)),
textAlign: TextAlign.center,
),
),
SizedBox(
height: 20,
),
Form(
key: formKey,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 30),
child: PinCodeTextField(
appContext: context,
pastedTextStyle: TextStyle(
color: Constant.color,
fontWeight: FontWeight.bold,
),
length: 6,
obscureText: false,
obscuringCharacter: '*',
blinkWhenObscuring: true,
animationType: AnimationType.fade,
validator: (v) {
if (v!.length < 6) {
return "Harap Masukan Kode OTP Yang Benar";
} else {
return null;
}
},
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 50,
fieldWidth: 40,
activeFillColor: Colors.white,
),
cursorColor: Colors.black,
animationDuration: Duration(milliseconds: 300),
errorAnimationController: errorController,
controller: textEditingController,
keyboardType: TextInputType.number,
boxShadows: [
BoxShadow(
offset: Offset(0, 1),
color: Colors.black12,
blurRadius: 10,
)
],
onCompleted: (v) {
print("Completed");
},
onChanged: (value) {
print(value);
setState(() {
currentText = value;
});
},
enablePinAutofill: true,
beforeTextPaste: (text) {
print("Allowing to paste $text");
//if you return true then it will show the paste confirmation dialog. Otherwise if false, then nothing will happen.
//but you can show anything you want here, like your pop up saying wrong paste format or etc
return true;
},
)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Text(
hasError ? "*Please fill up all the cells properly" : "",
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w400),
),
),
SizedBox(
height: 20,
),
showCountdown ?
Center(
child: CountdownTimer(
textStyle: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
endTime: endTime,
onEnd: () {
setState(() {
showCountdown = false;
});
},
),
)
:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Tidak Menerima Kode Verifikasi? ",
style: TextStyle(color: Colors.black54, fontSize: 15),
),
TextButton(
onPressed: () {
// userService.resendOtp(widget.phoneNumber.toString()).then((value) => snackBar(value));
setState(() {
showCountdown = true;
clicked++;
});
},
child: Text(
"Kirim Ulang",
style: TextStyle(
color: Constant.color,
fontWeight: FontWeight.bold,
fontSize: 16),
)
)
],
),
SizedBox(
height: 14,
),
Container(
margin:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 30),
child: ButtonTheme(
height: 50,
child: TextButton(
onPressed: () {
formKey.currentState!.validate();
// conditions for validating
if (currentText.length != 6 ) {
errorController!.add(ErrorAnimationType.shake); // Triggering error shake animation
setState(() => hasError = true);
} else {
userService.verifyOtp(widget.phoneNumber.toString(), currentText).then((value) {
if(value == "Kode OTP Salah") {
errorController!.add(ErrorAnimationType.shake);
textEditingController.clear();
setState(() {
hasError = true;
snackBar(value);
});
} else {
setState(() {
hasError = false;
snackBar(value);
});
Future.delayed(Duration(seconds: 3)).then( (value) =>
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new SignIn()))
);
}
});
}
},
child: Center(
child: Text(
"Verifikasi Kode OTP".toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold),
)),
),
),
decoration: BoxDecoration(
color: Constant.color,
borderRadius: BorderRadius.circular(5),
boxShadow: [
BoxShadow(
color: Colors.green.shade200,
offset: Offset(1, -2),
blurRadius: 5),
BoxShadow(
color: Colors.green.shade200,
offset: Offset(-1, 2),
blurRadius: 5)
]),
),
],
),
),
),
);
}
I dont have any idea why this is happened, so I need help for this. Thank you.
This error means that the Flutter Engine is instructed to build something (using setState for example), but at the very moment another build is already in progress.
So you have to find out which of your setState statements causes this error, it could be the one at onEnd, you can debug your code to be sure. Than you can add a callback, which will be executed after the current build is completed:
WidgetsBinding.instance.addPostFrameCallback((_){
setState(() {
});
});
If you haven't already done, include this in your main function:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp()); // or whatever you app class name is
}
I am new to Flutter here i'm trying to select all the square boxes, given below is the code for single selection of ListTile when one tile is selected it changes it's background color to redAccent, but i need code for multiple selection where i can select all three ListTile or either two ListTile and not only one
class MultiSelect extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: GradientAppBar(
title: Text('MultiSelect'),
),
body: MultipleSelectItems(),
);
}
}
class MultipleSelectItems extends StatefulWidget {
#override
_MultipleSelectItemsState createState() => _MultipleSelectItemsState();
}
class _MultipleSelectItemsState extends State<MultipleSelectItems> {
String selected = "First";
#override
Widget build(BuildContext context) {
return Container(
child: GestureDetector(
child: Column(
children: <Widget>[
SizedBox(
height: 40,
),
GestureDetector(
onTap: () {
setState(() {
selected = "First";
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
width: double.maxFinite,
decoration: BoxDecoration(
color: selected == 'First' ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'First',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
Padding(padding: EdgeInsets.only(top: 20)),
GestureDetector(
onTap: () {
setState(() {
selected = "Second";
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
width: double.maxFinite,
decoration: BoxDecoration(
color: selected == 'Second' ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'Second',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
Padding(padding: EdgeInsets.only(top: 20)),
GestureDetector(
onTap: () {
setState(() {
selected = "Third";
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
width: double.maxFinite,
decoration: BoxDecoration(
color: selected == 'Third' ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'Third',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
SizedBox(
height: 40,
),
MaterialButton(
child: Text("Submit"),
color: Colors.blueGrey,
textColor: Colors.white,
onPressed: () {},
),
],
),
),
);
}
}
I am taking the requirement as you don't want to toggle, but to select multiple items. This is the solution.
In Flutter, creating a different StatefulWidget for the buttons, will be unique for every button, and when you select the buttons. And hitting each button will have unique informations only. I know it is little confusing but follow this, and you will understand.
class MultipleSelectItems extends StatefulWidget {
#override
_MultipleSelectItemsState createState() => _MultipleSelectItemsState();
}
class _MultipleSelectItemsState extends State<MultipleSelectItems> {
// This is responsible to crate your buttons
// Every button is created will be having it's unique instance only
// Means, if you hit one button, it won't effect another, and you can select
// multiple
// And you don't have to declare your buttons multiple times in the code
// Which is indeed bad way of coding :)
List<Widget> get listTileWidgets{
List<Widget> _widget = [SizedBox(height: 40.0)];
List<String> _buttonName = ['First', 'Second', 'Third', 'Fourth'];
// ListTileWidget is defined below in another StatefulWidget
_buttonName.forEach((name){
_widget.add(ListTileWidget(name: name));
_widget.add(SizedBox(height: 20.0));
});
return _widget;
}
#override
Widget build(BuildContext context) {
return Material(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: this.listTileWidgets
)
)
);
}
}
// This will accept name of the button which will be used to be given
// plus maintaining the uniqueness
class ListTileWidget extends StatefulWidget{
final String name;
ListTileWidget({Key key, this.name}):super(key:key);
#override
ListTileWidgetState createState() => ListTileWidgetState();
}
class ListTileWidgetState extends State<ListTileWidget>{
bool isTapped = false;
#override
Widget build(BuildContext context){
return GestureDetector(
onTap: () {
setState(() => isTapped = true);
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
color: isTapped ? Colors.redAccent : Colors.lightBlueAccent,
width: double.maxFinite,
child: ListTile(
title: Text(
widget.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
)
)
)
)
);
}
}
Result you will get is below:
I am sorry that, I have not added your "Submit" button. So in order to make that thing visible in your code, simply add this in your Column only, and you will be good to go:
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
this.listTileWidgets,
SizedBox(height: 40),
MaterialButton(
child: Text("Submit"),
color: Colors.blueGrey,
textColor: Colors.white,
onPressed: () {},
)
]
)
That's pretty much it now.
I've got your answer. Changing the string variable to be an array can complete what you are looking to do. Then by checking if "First" or "Second" or "Third" is in the array we can determine what color the ListTile should be. Also when the ListTile is tapped we need to check if the array contains that string to determine if we are going to remove the string from the array (aka turning the color to blue) or if we need to add the string to the array (aka turning the color to red). Below I have included all of those changes to your class. Hope this answer helps! Comment if you have any questions
class MultipleSelectItems extends StatefulWidget {
#override
_MultipleSelectItemsState createState() => _MultipleSelectItemsState();
}
class _MultipleSelectItemsState extends State<MultipleSelectItems> {
var theSelected = ["First"];
#override
Widget build(BuildContext context) {
return Material(
child: Container(
child: GestureDetector(
child: Column(
children: <Widget>[
SizedBox(
height: 40,
),
GestureDetector(
onTap: () {
setState(() {
if(theSelected.contains("First")) {
theSelected.remove("First");
}
else {
theSelected.add("First");
}
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height: 100,
width: double.maxFinite,
decoration: BoxDecoration(
color: theSelected.contains('First') ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'First',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
Padding(padding: EdgeInsets.only(top: 20)),
GestureDetector(
onTap: () {
setState(() {
if(theSelected.contains("Second")) {
theSelected.remove("Second");
}
else {
theSelected.add("Second");
}
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
width: double.maxFinite,
decoration: BoxDecoration(
color: theSelected.contains('Second') ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'Second',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
Padding(padding: EdgeInsets.only(top: 20)),
GestureDetector(
onTap: () {
setState(() {
if(theSelected.contains("Third")) {
theSelected.remove("Third");
}
else {
theSelected.add("Third");
}
});
},
child: Container(
margin: EdgeInsets.only(left: 15, right: 15),
height:100,
width: double.maxFinite,
decoration: BoxDecoration(
color: theSelected.contains("Third") ? Colors.redAccent : Colors.lightBlueAccent,
),
child: ListTile(
title: Text(
'Third',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontFamily: 'WorksSansSemiBold',
fontWeight: FontWeight.bold,
),
),
),
),
),
SizedBox(
height: 40,
),
MaterialButton(
child: Text("Submit"),
color: Colors.blueGrey,
textColor: Colors.white,
onPressed: () {},
),
],
),
),
),
);
}
}