Displaying time selected from TimePicker to display Local Notification in Flutter App - flutter

I want to show Local Notification at the time that I have selected using TimePicker. I can call the function of scheduleAlaram() but I cannot send the time that i selected. Please help me out with how do I send this time to my function in order to display local notification.
This is my code:
import 'package:epicare/Medicines.dart';
import 'package:flutter/material.dart';
import 'package:flutter_switch/flutter_switch.dart';
class AddMedicine extends StatefulWidget {
#override
_AddMedicineState createState() => _AddMedicineState();
}
class _AddMedicineState extends State<AddMedicine> {
final timeController = TextEditingController();
Widget build(BuildContext context) {
String value = "";
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(),
body: SingleChildScrollView(
child: Column(
children: [
Container(
width: size.width,
padding: EdgeInsets.symmetric(horizontal: 34, vertical: 13),
child: Text(
'Reminder',
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 14,
color: const Color(0xff000000),
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.left,
),
),
Container(
width: size.width * 0.85,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: const Color(0xffffffff),
border: Border.all(width: 1.0, color: const Color(0xffdbe2ea)),
boxShadow: [
BoxShadow(
color: const Color(0x0a2c2738),
offset: Offset(0, 4),
blurRadius: 8,
),
],
),
child: TextField(
controller: timeController,
readOnly: true,
style: TextStyle(
fontFamily: 'Montserrat',
fontSize: 12,
color: const Color(0xff000000),
fontWeight: FontWeight.w500,
),
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () async {
var time = await showTimePicker(
builder: (BuildContext context, Widget child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.dark(
primary: const Color(0xffE5E0A1),
onPrimary: Colors.black,
surface: Colors.white,
onSurface: Colors.black,
),
dialogBackgroundColor:Colors.white,
),
child: child,
);
},
context: context,
initialTime: TimeOfDay.now(),
);
print(time.format(context));
timeController.text = time.format(context);
scheduleAlarm();
},
icon: Icon(
Icons.add_alert,
color: const Color(0xffd4d411),
),
),
contentPadding:
const EdgeInsets.symmetric(vertical: 18, horizontal: 20),
border: InputBorder.none,
hintText: "Add time(s) to eat your medicine",
hintStyle: TextStyle(
fontFamily: 'Montserrat',
fontSize: 13,
color: const Color(0xffcbd0d6),
),
),
onChanged: (text) {
value = text;
print(value);
},
),
),
],
),
),
);
}
void scheduleAlarm() async {
//print("hello");
var time = Time(2, 37, 0);
var androidPlatformChannelSpecifics =
AndroidNotificationDetails('repeatDailyAtTime channel id',
'repeatDailyAtTime channel name', 'repeatDailyAtTime description');
var iOSPlatformChannelSpecifics =
IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showDailyAtTime(
0,
'show daily title',
'Daily notification shown at approximately ${(time.hour)}:${(time.minute)}}',
time,
platformChannelSpecifics);
}
}
This is the screen which I am currently working on and I am on Reminder section to display notification:

how do I send this time to my function in order to display local notification.
Update showAlarm() to accept an argument for the time like so:
void scheduleAlarm(Time time) {
...
}
Since showDailyAtTime() accepts a value of Time, you should call the updated function after converting the value from the textcontroller to a Time value,
Time.format(context) will return a String so it cannot be used,
However you can modify this line var time = Time(2, 37, 0); to create a time value from the inputvalue and convert it to time using Time() which can then be sent to the scheduleAlarm() function

Related

Can i Select Multiple Accounts in a Banking app and pay them different amounts at the same time in flutter?

import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
import 'package:money_formatter/money_formatter.dart';
import 'package:shukela_app/api/banking_app_api.dart';
import 'package:shukela_app/screens/stokvel_detail.dart';
import 'package:shukela_app/screens/stokvels.dart';
import 'package:shukela_app/utils/constants.dart';
import 'package:shukela_app/utils/global_variables.dart';
import 'package:sizer/sizer.dart';
import 'package:shukela_app/model/bankingapp_model.dart';
import '../utils/user_preferences.dart';
class PayMultipleStokvelScreen extends StatefulWidget {
const PayMultipleStokvelScreen({Key? key}) : super(key: key);
#override
State<PayMultipleStokvelScreen> createState() =>
_PayMultipleStokvelScreenState();
}
TextEditingController txtSearch = TextEditingController();
TextEditingController txtAmount = TextEditingController();
class _PayMultipleStokvelScreenState extends State<PayMultipleStokvelScreen> {
String? selectedType;
bool hasText = false;
String buttonText = "PAY NOW";
bool isLoading = false;
Widget? showHideIcon() {
if (hasText) {
return IconButton(
icon: const Icon(
Icons.clear,
color: AppColors.primaryBlue,
),
onPressed: () {
txtSearch.clear();
setState(() {
hasText = false;
});
},
);
} else {
return null;
}
}
// void _showMultiSelectDialog(BuildContext context) async {
// await showDialog(
// context: context,
// builder: (ctx) {
// return MultiSelectDialog(
// items: _animals.map((e) => MultiSelectItem(e, e)).toList(),
// initialValue: _selectedAnimals,
// onConfirm: (values) {...},
// );
// },
// );
// }
double? balance;
final _formKey = GlobalKey<FormState>();
var selectedValue;
List<StokvelDetail> selectedStokvel = [];
#override
void initState() {
super.initState();
balance = double.parse(UserPreferences.getBalance() ?? '');
}
mf() {
MoneyFormatter mf = MoneyFormatter(amount: balance!);
return mf;
}
StokvelListState currentState = StokvelListState.showAllListState;
#override
Widget build(BuildContext context) => KeyboardDismisser(
gestures: const [GestureType.onTap],
child: SafeArea(
child: Scaffold(
backgroundColor: AppColors.secondaryColor,
appBar: AppBar(
backgroundColor: AppColors.secondaryColor,
elevation: 0,
title: const Text('Pay Multiple Stokvel',
style: screenTitleTextStyle),
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios,
color: AppColors.primaryBlue,
),
onPressed: () => Navigator.pop(context),
),
),
body: Form(
key: _formKey,
child: Column(
children: [
SizedBox(height: 5.h),
Container(
height: 6.h,
width: 98.w,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: TypeAheadFormField<Stokvel?>(
debounceDuration: const Duration(milliseconds: 500),
hideSuggestionsOnKeyboardHide: false,
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
constraints: BoxConstraints(maxHeight: 450),
color: AppColors.secondaryColor,
borderRadius: BorderRadius.all(Radius.circular(10))),
textFieldConfiguration: TextFieldConfiguration(
style: const TextStyle(
color: AppColors.primaryBlue,
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
controller: txtSearch,
onChanged: (value) {
setState(() {
hasText = true;
});
},
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
color: AppColors.primaryBlue,
),
suffixIcon: showHideIcon(),
hintText: 'Search Stokvel',
border: const OutlineInputBorder(
borderSide:
BorderSide(color: AppColors.primaryBlue),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide:
const BorderSide(color: AppColors.primaryBlue),
),
),
),
validator: (val) {
if (val!.isEmpty) {
return "Please select stokvel";
}
return null;
},
suggestionsCallback: BankingAppApi.getStokvelSuggestions,
itemBuilder: (context, Stokvel? suggestion) {
final stokvel = suggestion!;
//
return ListTile(
trailing: SizedBox(
height: 20.0,
width: 20.0,
child: Container(
color: AppColors.primaryBlue,
),
),
title: Text(
stokvel.stokvelName!,
style: const TextStyle(
fontFamily: Fonts.primaryFont,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue,
),
),
subtitle: Text(
stokvel.stokvelType!,
style: const TextStyle(
fontFamily: Fonts.primaryFont,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue),
),
);
},
noItemsFoundBuilder: (context) => const SizedBox(
height: 60,
child: Center(
child: Text(
'No Stokvel Found.',
style: TextStyle(fontSize: 20),
),
),
),
onSuggestionSelected: (Stokvel? suggestion) {
final stokvel = suggestion!;
setState(() {
txtSearch.text = stokvel.stokvelName!;
hasText = true;
});
stokvelID = stokvel.stokvelID;
memberID = stokvel.memberID;
},
),
),
SizedBox(
height: 4.h,
),
SizedBox(height: 3.h),
Container(
height: 6.h,
width: 98.w,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: TextFormField(
controller: txtAmount,
decoration: const InputDecoration(
labelStyle: TextStyle(
color: AppColors.primaryBlue,
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
suffixText: "ZAR",
border: OutlineInputBorder(
borderSide:
BorderSide(color: AppColors.primaryBlue)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1, color: AppColors.primaryBlue),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1, color: AppColors.primaryBlue),
),
labelText: 'Amount',
contentPadding:
EdgeInsets.only(left: 20, right: 15, bottom: 8),
),
keyboardType: TextInputType.number,
style: const TextStyle(
fontSize: 20.0,
fontFamily: Fonts.primaryFont,
color: AppColors.primaryBlue),
validator: (val) {
if (val!.isEmpty) {
return "Please enter amount";
}
return null;
},
),
),
SizedBox(height: 3.h),
Container(
height: 6.h,
width: 98.w,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: TextField(
readOnly: true,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: "Wallet Balance : R " + mf().output.nonSymbol,
border: const OutlineInputBorder(
borderSide:
BorderSide(color: AppColors.primaryBlue),
borderRadius: BorderRadius.horizontal()),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1, color: AppColors.primaryBlue),
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1, color: AppColors.primaryBlue),
),
),
),
),
SizedBox(height: 3.h),
Container(
height: 50,
width: 400,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.white)),
child: isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircularProgressIndicator(
color: AppColors.secondaryColor),
SizedBox(width: 24),
Text(
"Submitting payment...",
style: TextStyle(
color: AppColors.secondaryColor),
)
],
)
: Text(
buttonText,
style: const TextStyle(
fontFamily: Fonts.primaryFont,
fontWeight: FontWeight.bold,
color: AppColors.primaryBlue),
),
onPressed: () {
if (_formKey.currentState!.validate()) {
if (double.parse(txtAmount.text) <= balance!) {
setState(
() {
isLoading = true;
},
);
stokvelTransact.amount =
double.parse(txtAmount.text);
stokvelTransact.memberID = memberID;
stokvelTransact.stokvelID = stokvelID;
stokvelTransact.transactionTypeID = 1;
api
.stokvelDeposit(stokvelTransact,
"StokvelTransaction/StokvelTransact")
.then(
(value) => setState(
() {
Future.delayed(
const Duration(seconds: 3));
isLoading = false;
if (value == "Success") {
ScaffoldMessenger.of(context)
.showSnackBar(snackBar(
content:
'Payment made succesfully',
duration: 5));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(snackBar(
content:
'We have encountered technical problems, Try again later',
duration: 5));
}
},
),
)
.catchError(
(err) {
setState(() {
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
snackBar(
content: err.toString(), duration: 7));
},
);
} else {
ScaffoldMessenger.of(context).showSnackBar(snackBar(
content: "Insuficient funds!", duration: 7));
}
}
},
),
),
],
),
),
),
),
);
}
The above code is for the single page that I want to achieve this
This is what I want to achieve or any suggestions are accepted, I want to mark the Group of Stokvels I want to pay and pay them different accounts I don't know if it's possible please I'm new in flutter it a project I have to get it done as soon as possible this is the source code for my whole payment page, for now, I'm able to pay a single Stokvel Group but I want to select multiple
I highly suggest starting with formatting of the current code - its hard to read and with that hard to maintain.
The simple idea would be:
use List<StokvelDetail> selectedStokvel = []; you created to add the Stokvel's in every time you select Stokvel in SearchBar
Add Map<int,double> amounts = {}; <StokvelId, amount> to keep track of the amount you want to send to each Stokvel
Based on selectedStokvel build multiple 'amount window' widgets
Before submit verify walletBalance >= amounts.values.reduce((a,b)=> a + b)
For each selectedStokvel call api.stokvelDeposit(stokvelTransact, ...
I would primarly focus on spliting this code to multiple Widget classes, there are many schools but I like to have around 150 lines of code in single file at most.
From what i see your file can be easily split into:
Column(
children: [
SizedBox(),
SearchBar(),
SizedBox(),
AmountInput(), --> StokvelAmountInput() for each selectedStokvel
SizedBox(),
WalletBalance(),
SizedBox(),
SubmitButton(),
]),
```

type '(dynamic) => Null' is not a subtype of 'TextEditingController?'

I've been trying to accept user input in an extracted TextFormField method(Name, contact and email) and then save them to my firestore database using TextEditingController.
Here lies the case after accepting the input, the name and email controller always displays the same input. After saving, even for test purposes, the database always shows that only null values have been returned from the controllers...
Please help me solve this problem...
Snippet of the code:
class Profile extends StatefulWidget {
final String email;
Profile({Key key, this.email}) : super(key: key);
get user => AuthService();
#override
_ProfileState createState() => _ProfileState(email);
}
class _ProfileState extends State<Profile> {
String email;
String contain;
_ProfileState(this.email);
bool showPhone = false;
//Text editing controller
final TextEditingController _emailController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _nameController = TextEditingController();
// initState
#override
void initState() {
super.initState();
_emailController.addListener(() {
final String text = _emailController.text;
_emailController.value = _emailController.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
});
_phoneController.addListener(() {
final String contact = _phoneController.text;
_phoneController.value = _phoneController.value.copyWith(
text: contact,
selection: TextSelection(
baseOffset: contact.length,
extentOffset: contact.length,
),
composing: TextRange.empty,
);
});
_nameController.addListener(() {
final String contact = _nameController.text;
_nameController.value = _nameController.value.copyWith(
text: contact,
selection: TextSelection(
baseOffset: contact.length,
extentOffset: contact.length,
),
composing: TextRange.empty,
);
});
}
#override
void dispose() {
// Clean up the controller when the widget is removed from the
// widget tree.
_emailController.dispose();
_phoneController.dispose();
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final FirebaseAuth _auth = FirebaseAuth.instance;
bool loading = false;
final _formKey = GlobalKey<FormState>();
final doc = Database();
final ThemeData themeData = Theme.of(context);
String name, email, contact;
return loading
? Loading()
: SafeArea(
child: StreamBuilder<UserData>(
stream: Database(uid: doc.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
return Scaffold(
appBar: AppBar(
backgroundColor: Aqua,
elevation: 1,
title: Text(
'Edit Profile Info',
style: themeData.textTheme.headline1.copyWith(
color: White,
fontSize: 22,
letterSpacing: 0.75,
),
),
),
body: new Container(
height: window.physicalSize.height * 0.85,
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 25,
),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
new Center(
child: new Stack(
children: [
addVertical(20),
Container(
width: 190,
height: 190,
decoration: BoxDecoration(
border: Border.all(
width: 5,
color: Theme.of(context)
.scaffoldBackgroundColor,
),
boxShadow: [
BoxShadow(
blurRadius: 15,
spreadRadius: 5,
color: Cyan.withOpacity(0.5),
offset: Offset(0, 15),
),
],
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
"assets/images/IMG2.jpg"),
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
height: 55,
width: 55,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 5,
color: Theme.of(context)
.scaffoldBackgroundColor,
),
color: Cyan,
),
child: IconButton(
icon: Icon(
Icons.edit,
color: Dark_Blue,
),
onPressed:
() {}, // change profile picture
),
),
),
],
),
),
addVertical(30),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(
top: 25, left: 35, right: 35, bottom: 10),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
buildTextFormField(
"Full Name",
userData.fname ??
"Enter Full Name here...",
false,
false ?? null,
),
addVertical(15),
buildTextFormField(
"Contact",
userData.contact ?? "020 *** ****",
true,
false ?? null,
),
addVertical(15),
buildTextFormField(
"E-mail",
userData.email ?? "emaple123#gmail.com",
false,
true ?? null,
),
],
),
),
),
),
addVertical(35),
Padding(
padding: const EdgeInsets.only(
left: 35, right: 35, top: 25),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
primary: White,
),
onPressed: () {
Navigator.pop(context);
},
child: Text(
"CANCEL",
style: themeData.textTheme.bodyText1
.copyWith(
letterSpacing: 1.25,
fontWeight: FontWeight.bold,
fontSize: 18,
color: Mid_Blue,
),
),
),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() => loading = true);
dynamic result =
await _auth.currentUser;
// User person = result.user;
if (result != null) {
await Database(uid: doc.uid)
.updateData(
"$name",
"$email",
"$contact",
);
Toast.show(
"Your details have been successfully updated!",
context,
duration: Toast.LENGTH_LONG,
gravity: Toast.BOTTOM,
);
setState(() => loading = false);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Homepage()));
} else {
Toast.show(
"Please try again later",
context,
gravity: Toast.BOTTOM,
duration: Toast.LENGTH_LONG,
);
}
}
},
style: ElevatedButton.styleFrom(
elevation: 2,
primary: Colors.green[300],
shape: new RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
), //!elevatedButton background
),
child: Text(
"SAVE",
style: TextStyle(
fontSize: 18,
letterSpacing: 2.2,
color: White,
),
),
),
],
),
),
],
//),
),
),
),
);
} else {
return Loading();
}
},
),
);
}
Widget buildTextFormField(
String labelText,
String placeholder,
bool isPhone,
bool isEmail,
) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10),
child: TextField(
onSubmitted: isPhone || isEmail
? (value) {
value = _phoneController.toString() != null
? _emailController.toString() != null
: _nameController;
}
: null,
controller: isEmail || isPhone
? (value) {
value = _emailController.toString() != null
? _nameController.toString() != null
: _phoneController;
}
: _emailController,
obscureText: isPhone ? showPhone : false,
decoration: InputDecoration(
suffixIcon: isPhone
? IconButton(
onPressed: () => setState(() => showPhone = !showPhone),
icon: Icon(
Icons.remove_red_eye,
color: Red,
),
)
: null, //!This makes the icon appear only for the password field
contentPadding: EdgeInsets.only(bottom: 3),
labelText: labelText,
labelStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Aqua,
),
floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: placeholder,
hintStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Cyan,
),
),
keyboardType:
isPhone ? TextInputType.phone : TextInputType.emailAddress,
),
);
}
}
This is the function that builds name textField
buildTextFormField(
"Full Name",
userData.fname ?? "Enter Full Name here...",
false, // isPhone
false ?? null, // isEmail
),
From here we can see that isPhone = false; isEmail = false;
This is the function for the and controller in TextFormField:
controller: isEmail || isPhone
? (value) {
value = _emailController.toString() != null
? _nameController.toString() != null
: _phoneController;
}
: _emailController,
Since isEmail and isPhone are both false, it returns _emailController for name Textfield. So name is being updated on email textfield.
The function for onSubmitted() returns null if isEmail & isPhone are both false. Therefore, for name field, it will return null as the value (probably why you are getting null in the database).
I have tried to better organise the code below:
class MyHomePage extends StatefulWidget {
final String email;
MyHomePage({Key key, this.email}) : super(key: key);
get user => AuthService();
#override
_MyHomePageState createState() => _MyHomePageState(email);
}
class _MyHomePageState extends State<MyHomePage> {
String email;
// not used in this class
String contain;
_MyHomePageState(this.email);
bool showPhone = false;
//Text editing controller
final TextEditingController _emailController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _nameController = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is removed from the
// widget tree.
_emailController.dispose();
_phoneController.dispose();
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final FirebaseAuth _auth = FirebaseAuth.instance;
bool loading = false;
final _formKey = GlobalKey<FormState>();
final doc = Database();
final ThemeData themeData = Theme.of(context);
return loading
? Loading()
: SafeArea(
child: StreamBuilder<UserData>(
stream: Database(uid: doc.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
_emailController.text = userData.email;
_phoneController.text = userData.phone;
_nameController.text = userData.name;
return Scaffold(
appBar: AppBar(
backgroundColor: Aqua,
elevation: 1,
title: Text(
'Edit Profile Info',
style: themeData.textTheme.headline1.copyWith(
color: White,
fontSize: 22,
letterSpacing: 0.75,
),
),
),
body: new Container(
height: window.physicalSize.height * 0.85,
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 25,
),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
new Center(
child: new Stack(
children: [
addVertical(20),
Container(
width: 190,
height: 190,
decoration: BoxDecoration(
border: Border.all(
width: 5,
color: Theme.of(context)
.scaffoldBackgroundColor,
),
boxShadow: [
BoxShadow(
blurRadius: 15,
spreadRadius: 5,
color: Cyan.withOpacity(0.5),
offset: Offset(0, 15),
),
],
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
"assets/images/IMG2.jpg"),
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
height: 55,
width: 55,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 5,
color: Theme.of(context)
.scaffoldBackgroundColor,
),
color: Cyan,
),
child: IconButton(
icon: Icon(
Icons.edit,
color: Dark_Blue,
),
onPressed:
() {}, // change profile picture
),
),
),
],
),
),
addVertical(30),
Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.only(
top: 25, left: 35, right: 35, bottom: 10),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
buildTextFormField(
"Full Name",
"Enter Full Name here...",
false,
_nameController,
),
addVertical(15),
buildTextFormField(
"Contact",
"020 *** ****",
true,
_phoneController,
),
addVertical(15),
buildTextFormField(
"E-mail",
"example123#gmail.com",
false,
_nameController,
),
],
),
),
),
),
addVertical(35),
Padding(
padding: const EdgeInsets.only(
left: 35, right: 35, top: 25),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
primary: White,
),
onPressed: () {
Navigator.pop(context);
},
child: Text(
"CANCEL",
style: themeData.textTheme.bodyText1
.copyWith(
letterSpacing: 1.25,
fontWeight: FontWeight.bold,
fontSize: 18,
color: Mid_Blue,
),
),
),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() => loading = true);
dynamic result =
await _auth.currentUser;
// User person = result.user;
if (result != null) {
await Database(uid: doc.uid)
.updateData(
_nameController.text,
_emailController.text,
_phoneController.text,
);
Toast.show(
"Your details have been successfully updated!",
context,
duration: Toast.LENGTH_LONG,
gravity: Toast.BOTTOM,
);
setState(() => loading = false);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
Homepage()));
} else {
Toast.show(
"Please try again later",
context,
gravity: Toast.BOTTOM,
duration: Toast.LENGTH_LONG,
);
}
}
},
style: ElevatedButton.styleFrom(
elevation: 2,
primary: Colors.green[300],
shape: new RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
), //!elevatedButton background
),
child: Text(
"SAVE",
style: TextStyle(
fontSize: 18,
letterSpacing: 2.2,
color: White,
),
),
),
],
),
),
],
//),
),
),
),
);
} else {
return Loading();
}
},
),
);
}
Widget buildTextFormField(
String labelText,
String placeholder,
bool isPhone,
TextEditingController controller,
) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10),
child: TextField(
// onSubmitted: isPhone || isEmail
// ? (value) {
// value = _phoneController.toString() != null
// ? _emailController.toString() != null
// : _nameController;
// }
// : null,
controller: controller,
obscureText: isPhone ? showPhone : false,
decoration: InputDecoration(
suffixIcon: isPhone
? IconButton(
onPressed: () => setState(() => showPhone = !showPhone),
icon: Icon(
Icons.remove_red_eye,
color: Red,
),
)
: null, //!This makes the icon appear only for the password field
contentPadding: EdgeInsets.only(bottom: 3),
labelText: labelText,
labelStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Aqua,
),
floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: placeholder,
hintStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Cyan,
),
),
keyboardType:
isPhone ? TextInputType.phone : TextInputType.emailAddress,
),
);
}
}

How to add user-submitted tags data from "material_tag_editor" into a "Flutter Form Builder" form?

I am building a form using the "Flutter Form Builder" package 4.0.2 and trying to add two fields where users enter "tags" via the "material_tag_editor" package 0.0.6
The Problem: when then form is submitted by pressing the "Post" button, neither of the data submitted for those "tag" form fields (Q1 or Q3) is included (see screenshot of the console below).
Notice the line "flutter: {qFour: 30, qFive: sample answer to q5, qTen: sample answer to q10}" - neither Q1 nor Q3 are included (I added their data in separate print statements, so you see them in the console - look for the >>> lines).
Here are screenshots of the form with sample tags entered (iPhone simulator screenshot), and the bottom of the form with the button:
Here's the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:streakers_journal_beta/screens/reviews_screen.dart';
import 'package:streakers_journal_beta/screens/welcome_screen.dart';
import 'package:streakers_journal_beta/models/user.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
// BEGIN code from material_tag_editor
import 'package:material_tag_editor/tag_editor.dart';
import 'package:material_tag_editor/tag_editor_layout_delegate.dart';
import 'package:material_tag_editor/tag_layout.dart';
import 'package:material_tag_editor/tag_render_layout_box.dart';
// END code from material_tag_editor
//import 'dart:html';
//import 'dart:convert';
// This is the stateful widget that the main application instantiates, per https://api.flutter.dev/flutter/widgets/Form-class.html
class SandboxWriteReviewScreen extends StatefulWidget {
// BEGIN code from material_tag_editor
final String title = 'Material Tag Editor Demo';
// END code from material_tag_editor
#override
_SandboxWriteReviewScreenState createState() =>
_SandboxWriteReviewScreenState();
}
// This is the private State class that goes with WriteReviewScreen
class _SandboxWriteReviewScreenState extends State<SandboxWriteReviewScreen> {
var data;
AutovalidateMode autovalidateMode = AutovalidateMode.always;
bool readOnly = false;
bool showSegmentedControl = true;
//final _newFormbuilderKey = GlobalKey<FormState>();
final _newnewFormbuilderKey = GlobalKey<FormBuilderState>();
// above "GlobalKey" lets us generate a unique, app-wide ID that we can associate with our form, per https://fluttercrashcourse.com/blog/realistic-forms-part1
final ValueChanged _onChanged = (val) => print(val);
// BEGIN related to FormBuilderTextField in form below
final _ageController = TextEditingController(text: '45');
bool _ageHasError = false;
// END related to FormBuilderTextField in form below
String qEleven;
String qTwelve;
// BEGIN code from material_tag_editor
List<String> qOne = [];
final FocusNode _focusNode = FocusNode();
onDelete(index) {
setState(() {
qOne.removeAt(index);
});
}
// below = reiteration for cons
List<String> qThree = [];
//final FocusNode _focusNode = FocusNode();
uponDelete(index) {
// NOTE: "uponDelete" for cons vs. "onDelete" for pros
setState(() {
qThree.removeAt(index);
});
}
// END code from material_tag_editor
//final _user = User();
List<bool> isSelected;
int starIconColor =
0xffFFB900; // was 0xffFFB900; 0xffD49428 is from this image: https://images.liveauctioneers.com/houses/logos/lg/bartonsauction550_large.jpg?auto=webp&format=pjpg&width=140
#override
void initState() {
//isSelected = [true, false];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
Padding(
padding: EdgeInsets.only(right: 12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.keyboard_backspace),
onPressed: () {
Navigator.pop(context);
},
),
Text(
'back',
style: TextStyle(
fontSize: 7,
),
),
],
),
),
],
leading: Icon(
Icons.rate_review,
color: Colors.black54,
),
title: Column(
children: [
Text(
'SANDBOX Write a Review',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
SizedBox(
height: 6.0,
),
Text(
'flutter_form_builder ^4.0.2',
style: TextStyle(
color: Colors.limeAccent,
fontSize: 14,
),
),
SizedBox(
height: 6.0,
),
],
),
// BEGIN appBar gradient code, per https://medium.com/flutter-community/how-to-improve-your-flutter-application-with-gradient-designs-63180ba96124
flexibleSpace: Container(
decoration: BoxDecoration(
color: Colors.indigoAccent,
),
),
backgroundColor: Colors.white,
centerTitle: false,
),
body: SingleChildScrollView(
child: Container(
child: Builder(
builder: (context) => FormBuilder(
// was "builder: (context) => Form("
key: _newnewFormbuilderKey,
initialValue: {
'date': DateTime.now(),
},
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 12.0,
),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q1 via TagEditor', // was 'What are 3 good or positive things about the house, property or neighborhood?', // [ 1 ​]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
// BEGIN code from material_tag_editor
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TagEditor(
length: qOne.length,
delimiters: [
','
], // was delimiters: [',', ' '], Also tried "return" ('\u2386',) and '\u{2386}'
hasAddButton: true,
textInputAction: TextInputAction
.next, // moves user from one field to the next!!!!
autofocus: false,
maxLines: 1,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.lightBlue),
// borderRadius: BorderRadius.circular(20.0),
// ),
inputDecoration: const InputDecoration(
// below was "border: InputBorder.none,"
isDense: true,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
// above is per https://github.com/flutter/flutter/issues/5191
),
labelText: 'separate, with, commas',
labelStyle: TextStyle(
fontStyle: FontStyle.italic,
backgroundColor:
Color(0x65dffd02), // was Color(0xffDDFDFC),
color: Colors.black87, // was Color(0xffD82E6D),
fontSize: 14,
),
),
onTagChanged: (value) {
setState(() {
qOne.add(value);
});
},
tagBuilder: (context, index) => _Chip(
index: index,
label: qOne[index],
onDeleted: onDelete,
),
),
),
// END code from material_tag_editor
SuperDivider(),
// END Chips Input
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'​​Q3 via TagEditor (skipped Q2, for simplicity)', // [ 2 ​] was '​​List up to 3 negatives, or things you don’t like, about the house, property or neighborhood:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: '(optional)', // was text: '\n(optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
backgroundColor:
Color(0x70DDFDFC), // was Color(0x30F8A0A2),
color: Colors.black54, // was Color(0xffD82E6D),
//color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
// BEGIN code from material_tag_editor
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TagEditor(
length: qThree.length,
delimiters: [','], // was delimiters: [',', ' '],
hasAddButton: true,
textInputAction: TextInputAction
.next, // moves user from one field to the next!!!!
autofocus: false,
maxLines: 1,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.lightBlue),
// borderRadius: BorderRadius.circular(20.0),
// ),
inputDecoration: const InputDecoration(
// below was "border: InputBorder.none,"
isDense: true,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
// above is per https://github.com/flutter/flutter/issues/5191
),
labelText: 'separate, with, commas',
labelStyle: TextStyle(
fontStyle: FontStyle.italic,
backgroundColor:
Color(0x65dffd02), // was Color(0xffDDFDFC),
color: Colors.black87, // was Color(0xffD82E6D),
fontSize: 14,
),
),
onTagChanged: (value) {
setState(() {
qThree.add(value);
});
},
tagBuilder: (context, index) => _Chip(
index: index,
label: qThree[index],
onDeleted: uponDelete,
),
),
),
// END code from material_tag_editor
SuperDivider(),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'​​Q4 - via FormBuilder\'s FormBuilderRadioGroup', // [ 3 ​]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (required)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.red[700],
), // was 'misleading or inaccurate?',
),
],
),
),
FormBuilderRadioGroup(
name: 'qFour',
decoration: const InputDecoration(
border: InputBorder.none,
labelStyle: TextStyle(fontStyle: FontStyle.italic),
),
wrapVerticalDirection: VerticalDirection.down,
// orientation: GroupedRadioOrientation.vertical,
orientation: OptionsOrientation.vertical,
onChanged: _onChanged,
options: [
FormBuilderFieldOption(
value: '0', child: Text('Never')),
FormBuilderFieldOption(
value: '30', child: Text('Within the last month')),
FormBuilderFieldOption(
value: '180',
child: Text('Within the last 6 months')),
FormBuilderFieldOption(
value: '181',
child: Text('More than 6 months ago')),
],
),
SuperDivider(),
Center(
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q5 - via FormBuilder\'s FormBuilderTextField', // [ 4 ​]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
),
GavTextField(
maxCharLength: 200,
fieldAttribute: 'qFive',
fieldLabelText: '',
),
SuperDivider(),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q10 - via FormBuilder\'s FormBuilderTextField (skipped Q6 - Q9, for simplicity)', // [ 9 ​]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
GavTextField(
maxCharLength: 1200,
fieldAttribute: 'qTen',
fieldLabelText:
'Be honest & kind.', // was 'Be honest, but kind.',
),
SuperDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.purple,
padding: EdgeInsets.symmetric(
horizontal: 50, vertical: 20),
textStyle: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
onPressed: () {
_newnewFormbuilderKey.currentState.save();
if (_newnewFormbuilderKey.currentState
.validate()) {
print(_newnewFormbuilderKey.currentState.value);
print(
' >>> Q1\'s value via separate print: {$qOne}',
);
print(
' >>> Q3\'s value via separate print: {$qThree}',
);
} else {
print("validation failed");
}
},
child: Text(
'Post',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
],
),
),
SizedBox(
height: 200.0,
),
],
),
),
),
),
),
),
);
}
}
class GavTextField extends StatelessWidget {
GavTextField(
{#required this.maxCharLength,
#required this.fieldAttribute,
#required this.fieldLabelText});
int maxCharLength;
String fieldAttribute;
String fieldLabelText;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FormBuilderTextField(
name: '$fieldAttribute',
// BEGIN countdown to max number of characters, per https://stackoverflow.com/a/64035861/1459653
maxLength: maxCharLength,
maxLines: null,
buildCounter: (
BuildContext context, {
int currentLength,
int maxLength,
bool isFocused,
}) {
return Text(
'${maxLength - currentLength}',
);
},
// END countdown to max number of characters, per https://stackoverflow.com/a/64035861/1459653
decoration: InputDecoration(
labelText:
'$fieldLabelText', // was " Separate items, with, commas",
//counterText: _textController.text.length.toString(),
labelStyle: TextStyle(
fontSize: 12.5,
fontStyle: FontStyle.italic,
),
//helperText: 'Separate, with, commas',
//floatingLabelBehavior: ,
// filled: true,
// fillColor: Colors.lightBlue.withOpacity(0.05),
// BEGIN change border if focus
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: BorderRadius.circular(20.0),
),
// END change border if focus
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide(),
),
),
textInputAction:
TextInputAction.next, // moves user from one field to the next!!!!
autofocus:
false, // on screen load, first text field is already active - user can just start typing
),
);
}
} //</formstate>`
var alertStyle = AlertStyle(
animationType: AnimationType.fromTop,
isCloseButton: true,
isOverlayTapDismiss: true,
descTextAlign: TextAlign.start,
alertBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
side: BorderSide(
color: Colors.grey,
),
),
titleStyle: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 16,
color: Colors.black54,
),
alertAlignment: Alignment.topCenter,
);
// BEGIN code from material_tag_editor
class _Chip extends StatelessWidget {
const _Chip({
#required this.label,
#required this.onDeleted,
#required this.index,
});
final String label;
final ValueChanged<int> onDeleted;
final int index;
#override
Widget build(BuildContext context) {
return Chip(
backgroundColor: Colors.blueGrey.shade100,
labelPadding: const EdgeInsets.only(left: 8.0),
label: Text(label),
deleteIcon: Icon(
Icons.cancel_rounded, // was "Icons.close,"
size: 18,
),
onDeleted: () {
onDeleted(index);
},
);
}
}
// END code from material_tag_editor
class SuperDivider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 4.0,
bottom: 4.0,
),
child: const Divider(
color: Colors.white70,
height: 30,
thickness: 0.1,
indent: 0,
endIndent: 0,
),
);
}
}
Flutter form builder assumes that only form children's will be used which automatically updates ancestor form whenever their value is changed. But since tag builder is not a form widget, you can do two things -
Wrap these widgets inside a new form widget, whose responsibility will be to only send updates to the ancestor form and render its child.
You can do something like
class GenericFormWidget<T> extends StatefulWidget {
GenericFormWidget({
Key key,
#required this.attribute,
#required this.builder,
}) : super(key: key);
final String attribute;
final Function(BuildContext, Function(T value)) builder;
#override
Widget build(BuildContext context) {
return widget.builder(context, _updateValue);
}
void _updateValue(T value) =>
_formState.setAttributeValue(widget.attribute, value);
}
Instead of using flutter form builder, you can have use Flutter form and create your own field UI using material widgets. This will be costly for you since you will need to migrate existing fields too.

How to pass Stream<String> values as parameters to a function created in a bloc file FLUTTER

I have a login bloc file where I have created 2 streams username and password
class LoginBloc {
LoginRepository _loginRepository = new LoginRepository();
StreamController _loginController = StreamController<Response<LoginModel>>();
//declare streams
final _userName = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
//get data from streams
StreamSink<Response<LoginModel>> get loginControllerSink =>
_loginController.sink;
Stream<String> get loginUserName => _userName.stream;
Stream<String> get loginPassword => _password.stream;
Stream<bool> get submitLoginForm =>
Rx.combineLatest2(loginUserName, loginPassword, (a, b) => true);
Function(String) get changeLoginUserName => _userName.sink.add;
Function(String) get changeLoginPassword => _password.sink.add;
//dispose
dispose() {
_userName.close();
_password.close();
}
//functions
login(email, password, context) async {
try {
LoginModel data = await _loginRepository.loginIntoSystem(email, password);
loginControllerSink.add(Response.completed(data));
Navigator.pushNamed(context, '/dashboard');
} catch (e) {
loginControllerSink.add(Response.error(e.toString()));
print(e);
}
}
}
Here's the login.dart
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
bool _passwordVisible = false;
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final LoginBloc bloc = LoginProvider.of(context);
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: SafeArea(
child: Center(
child:
Column(
children: <Widget>[
SizedBox(height: size.height*0.05),
Icon(
Icons.track_changes,
size: size.width*0.06,
),
SizedBox(height: size.height*0.02),
Text('Login',
style: TextStyle(
color: Color(0xff005D6C),
fontSize: size.width*0.02,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
SizedBox(height: size.height*0.04),
Container(
width: size.width * 0.2,
padding: EdgeInsets.only(bottom: 20.0),
child: StreamBuilder<String>(
stream: bloc.loginUserName,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeLoginUserName,
decoration: new InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 15.0),
border: new OutlineInputBorder(
borderSide: const BorderSide(
width: 2.0, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(50.0)),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 2.0),
borderRadius: BorderRadius.circular(50.0),
),
hintText: 'User Name',
hintStyle: new TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold),
suffixIcon: const Icon(
Icons.person,
size: 30.0,
color: Colors.grey,
),
errorText: snapshot.error),
);
}),
),
Container(
width: size.width * 0.2,
padding: EdgeInsets.only(bottom: 20.0),
child: StreamBuilder<String>(
stream: bloc.loginPassword,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeLoginPassword,
obscureText: !_passwordVisible,
maxLength: 20,
decoration: new InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 15.0),
border: new OutlineInputBorder(
borderSide: const BorderSide(
width: 2.0, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(50.0)),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Colors.grey, width: 2.0),
borderRadius: BorderRadius.circular(50.0),
),
hintText: 'Password',
hintStyle: new TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold),
prefixIcon: const Icon(
Icons.lock,
size: 30.0,
color: Colors.grey,
),
suffixIcon: IconButton(
icon: Icon(
// Based on passwordVisible state choose the icon
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
errorText: snapshot.error),
);
}),
),
Container(
width: size.width * 0.2,
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: StreamBuilder<bool>(
stream: bloc.submitLoginForm,
builder: (context, snapshot){
return FlatButton(
color: Color(0XFFEFEFEF),
textColor: primaryColor,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.symmetric(
vertical: 12.0, horizontal: 10.0),
onPressed: () => snapshot.hasError ? null : bloc.login(bloc.loginUserName, bloc.loginPassword, context) ,
child: Text(
"Login",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic),
),
);
},
),
),
)
],
)
),
),
),
);
}
}
I am calling the login function on CLick of login button and passing the username, password and context as parameters. But on click its giving "Expected a value of type 'String', but got one of type 'BehaviorSubject'"
added the loginIntoSystem:
Future<LoginModel> loginIntoSystem(String email, String password)
async {
dynamic body = {
'email': email,
'password': password
};
final response = await _provider.post("/login", body);
return LoginModel.fromJson(response);
}
Is there a way to convert BehaviorSubject to String ?
I found the solution.
By using value property, I was able to get the values.
final fEmail = _email.value;
final fPassword = _password.value;
Thank you everyone for taking your time and posting the answers.
You are right. I was wrong, and behavior subject has a property value which returns value of that behavior subject. You can use this to get the email and password out of your behavior subject. Also, since your bloc file already has access to those variables, you don't have to pass them as arguments from your onPressed function.
login(context) async {
final email = _userName.value;
final password = _password.value;
try {
LoginModel data = await _loginRepository.loginIntoSystem(email, password);
loginControllerSink.add(Response.completed(data));
Navigator.pushNamed(context, '/dashboard');
} catch (e) {
loginControllerSink.add(Response.error(e.toString()));
print(e);
}
}

flutter search in firestore for two items

I'm developing a flight ticket booking app with flutter and I'm trying to add a search query from Firestore for the start and destination. basically the idea is for the user to choose his starting and destination location by searching and when he select his starting station it will be stored in selected item and this particular item will not be found in the destination items no longer.
can you show me how to accomplish this task?
You can implement search text by using this code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:trackingsystem/Director/Director_View_Job_Details.dart';
class DirectorViewJob extends StatefulWidget {
#override
_DirectorViewJobState createState() => new _DirectorViewJobState();
}
class _DirectorViewJobState extends State<DirectorViewJob> {
var jobTitle = [];
var jobDescription = [];
var jobSalary = [];
var jobExperience = [];
var jobDeadlineDate = [];
var companyName = [];
bool dataCheck = false;
bool isLoading = false;
var jobDetails;
var id;
var documnet = [];
List products = [];
var productId = [];
var deleteTitle;
void getJobs() async {
setState(() {
isLoading = true;
});
Firestore.instance
.collection("CreateJob")
.getDocuments()
.then((querySnapshot) {
querySnapshot.documents.forEach((result) {
id = result.documentID;
products.add(result.data['Job Details']);
documnet.add(id);
jobDetails = (result.data["Job Details"]);
jobTitle.add(jobDetails['title']);
jobDescription.add(jobDetails['description']);
jobSalary.add(jobDetails['salary']);
jobDeadlineDate.add(jobDetails['date']);
print("date $jobDeadlineDate");
jobExperience.add(jobDetails['Experience_Level']);
companyName.add(jobDetails['companyName']);
if (jobDetails.isNotEmpty) {
setState(() {
dataCheck = true;
});
} else {
isLoading = true;
}
});
setState(() {
isLoading = false;
});
});
}
searchJob(q) {
print(q);
print('products $products');
for (int i = 0; i < products.length; i++) {
if (products[i]['title'] == "$q") {
setState(() {
jobTitle.clear();
jobSalary.clear();
jobDescription.clear();
jobDeadlineDate.clear();
jobTitle.add(products[i]['title']);
jobDescription.add(products[i]["description"]);
jobSalary.add(products[i]['salary']);
jobDeadlineDate.add(products[i]['date']);
});
}
}
}
#override
void initState() {
getJobs();
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.blueGrey[600],
title: Text(
"Jobs",
style: TextStyle(fontFamily: 'Righteous'),
),
),
backgroundColor: Colors.white,
body: isLoading
? SpinKitDoubleBounce(
color: Colors.blueGrey,
)
: Center(
child: ListView(
children: dataCheck
? <Widget>[
SizedBox(
height: 20,
),
SizedBox(
height: 10,
),
Container(
child: TextField(
// controller: _searchQueryController,
autofocus: true,
decoration: InputDecoration(
labelText: "Search Job",
hintText: "Search",
prefixIcon: Icon(
Icons.search,
color: Colors.blueGrey[400],
),
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius:
new BorderRadius.circular(25.0),
borderSide: new BorderSide(),
),
//fillColor: Colors.green
),
style: TextStyle(
color: Colors.blueGrey[300],
fontSize: 16.0),
onChanged: (query) => {searchJob(query)},
),
),
SizedBox(
height: 10,
),
Container(
decoration: BoxDecoration(
// color: Colors.lightGreen[100],
),
child: new ListView.builder(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: jobTitle?.length ?? 0,
itemBuilder: (BuildContext context, index) {
return InkWell(
splashColor: Colors.white,
onTap: () async {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
DirectorJobDetails(
jobDetailName:
jobTitle[index],
jobDetailDescription:
jobDescription[
index],
jobDetailExperience:
jobExperience[
index],
jobDetailDate:
jobDeadlineDate[
index],
jobDetailSalary:
jobSalary[index],
jobDetailCompany:
companyName[index],
documentId:
documnet[index],
)),
);
},
child: new Card(
color: Colors.blueGrey[200],
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(30.0),
),
child: Container(
decoration: BoxDecoration(),
child: ListTile(
leading: Icon(Icons.work,
size: 40),
title: Text(jobTitle[index],
style: TextStyle(
color: Colors.black,
fontSize: 23.0,
// fontWeight: FontWeight.bold,
fontFamily:
'Righteous',
//decoration: TextDecoration.none
)),
subtitle: Row(children: [
SizedBox(
height: 10,
),
Expanded(
child: Text(
"Salary : ${jobSalary[index]}",
style: TextStyle(
color:
Colors.black,
fontSize: 18.0,
// fontWeight: FontWeight.bold,
fontFamily:
'Righteous',
//decoration: TextDecoration.none
)),
),
Expanded(
child: Text(
"DeadLine Date : ${jobDeadlineDate[index]}",
style: TextStyle(
color:
Colors.black,
fontSize: 18.0,
// fontWeight: FontWeight.bold,
fontFamily:
'Righteous',
//decoration: TextDecoration.none
)),
)
]),
//
))));
}),
),
SizedBox(
height: 20,
)
]
: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(
50.0, 100.0, 50.0, 50.0),
child: Center(
child: Text(
"No Jobs are available",
style: TextStyle(
color: Colors.black,
fontSize: 25.0,
fontWeight: FontWeight.bold,
fontFamily: 'Righteous',
//decoration: TextDecoration.none
),
),
))
]),
)),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
You can use limit in your code Like this:
FirebaseFirestore.instance.collection('test').limit(2).get()
and you'll get only two elements