Flutter Riverpod Statenotifier should rebuild on map changes - flutter

My statenotifier does not rebuild when the map changes. I think the issue is that the map is inside another class. I use riverpod and i've seen it work with lists so I tested with a List inside a class and filter rebuilds to that class. But no success
StateNotifier using copyWith to replace a map does not trigger rebuild
class Admin {
const Admin({
this.admin = const {},
});
final Map<int, List<String>> admin;
Admin copyWith({
Map<int, List<String>>? admin,
}) {
return Admin(
admin: admin ?? this.admin,
);
}
}
class Member {
const Member({
this.wordOfTheDay = "",
this.admin = const Admin(),
});
final Admin admin;
final String wordOfTheDay;
Member copyWith({
Admin? admin,
String? wordOfTheDay,
}) {
return Member(
admin: admin ?? this.admin,
wordOfTheDay: wordOfTheDay ?? this.wordOfTheDay,
);
}
}
class MemberNotifier extends StateNotifier<AsyncValue<Member>> {
MemberNotifier() : super(const AsyncValue.loading());
final Map<int, List<String>> admins = {};
void loadMembers(String id, String item) {
admins[id]!.add(item);
state = state.whenData((value) => value.copyWith(admin: state.value!.admin.copyWith(admin: admins)));
}
}
final watch = ref.watch(memberNotifierProvider.select((member) => member.value!.admin));

We can pass the Map values and set the values in Map<String,dynamic> like this:-
Provider
final counterProvider =
StateNotifierProvider<Counter, Map<String, dynamic>>((_) => Counter());
class Counter extends StateNotifier<Map<String, dynamic>> {
// Counter(Map<String, dynamic> data): super(data);
Counter() : super({});
void displayFile(String name, String email, String address) {
Map<String, dynamic> cartMap = {"name": name, "email": email, "address": address};
state = {...state, ...cartMap};
}
}
After that we can fetch the values of Map using this way in Riverpod with HooksConsumerWidget:-
class MyHomePage extends HookConsumerWidget {
MyHomePage({Key? key}) : super(key: key);
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController addressController = TextEditingController();
#override
Widget build(BuildContext context, WidgetRef ref) {
final dataValue = ref.watch(counterProvider);
String name = dataValue['name'].toString();
String email = dataValue['email'].toString();
String address = dataValue['address'].toString();
print(dataValue);
return Scaffold(
appBar: AppBar(
title: const Text(' demo'),
),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: [
TextFormField(
controller: nameController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter name',
),
),
const SizedBox(
height: 10,
),
TextFormField(
controller: emailController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter email',
),
),
const SizedBox(
height: 10,
),
TextFormField(
controller: addressController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter address',
),
),
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).displayFile(
nameController.text,
emailController.text,
addressController.text);
},
child: const Text('Show Details')),
const SizedBox(
height: 20,
),
Text(name),
const SizedBox(
height: 10,
),
Text(email),
const SizedBox(
height: 10,
),
Text(address),
],
),
));
}
}

Related

Save input values between widget rebuilds with Bloc Flutter

I have a form builded with Bloc package.
There are two options with textfields in it.
Switching between option i've made also with bloc and Visibility widget.
When I choose an option widget rebuilds, name and price values deletes.
What is the best way to save this values between choosing options?
Here is my Bloc code
class FormBloc extends Bloc<FormEvent, MyFormState> {
FormBloc() : super(MyFormState()) {
on<RadioButtonFormEvent>(_setRadioButtonState);
}
void _setRadioButtonState(
RadioButtonFormEvent event, Emitter<MyFormState> emit) async {
emit(RadioButtonFormState(
buttonIndex: event.buttonIndex,
buttonName: event.buttonName,
));
}
}
class MyFormState {}
class RadioButtonFormState extends MyFormState {
final int buttonIndex;
final String buttonName;
RadioButtonFormState({
required this.buttonIndex,
required this.buttonName,
});
}
abstract class FormEvent extends Equatable {}
class RadioButtonFormEvent extends FormEvent {
final int buttonIndex;
final String buttonName;
RadioButtonFormEvent({
required this.buttonIndex,
required this.buttonName,
});
#override
List<Object?> get props => [buttonIndex, buttonName,];
}
Here is Form code
class FormInput extends StatelessWidget {
const FormInput({super.key});
#override
Widget build(BuildContext context) {
final formBlocWatcher = context.watch<FormBloc>().state;
final nameController = TextEditingController();
final priceController = TextEditingController();
final formOneController = TextEditingController();
final formTwoController = TextEditingController();
final formThreeController = TextEditingController();
String formOptionController = '';
bool optionOneIsActive = true;
bool optionTwoIsActive = false;
if (formBlocWatcher is RadioButtonFormState) {
switch (formBlocWatcher.buttonIndex) {
case 0:
formOptionController = formBlocWatcher.buttonName;
break;
case 1:
optionTwoIsActive = true;
optionOneIsActive = false;
formOptionController = formBlocWatcher.buttonName;
break;
}
}
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 15,
left: 15,
right: 15),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(hintText: 'Name'),
),
const SizedBox(height: 10),
TextField(
controller: priceController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Price'),
),
const SizedBox(height: 15),
OptionsWidget(),
Visibility(
visible: optionOneIsActive,
child: TextField(
controller: formOneController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form one'),
)),
Visibility(
visible: optionTwoIsActive,
child: Column(
children: [
TextField(
controller: formTwoController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form two'),
),
TextField(
controller: formThreeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form three'),
),
],
)),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
BlocProvider.of<ProductsListBloc>(context).add(
AddProductEvent(
productName: nameController.text,
productPrice: int.parse(priceController.text),
productDescOne: formOneController.text,
productDescTwo: formTwoController.text,
productDescThree: formThreeController.text,
formOption: formOptionController,
),
);
},
child: Text('Create New'),
),
],
),
);
}
}
class OptionsWidget extends StatelessWidget {
OptionsWidget({super.key});
int value = 0;
Widget CustomRadioButton(String text, int index, BuildContext context) {
final formBloc = BlocProvider.of<FormBloc>(context);
final blocWatch = context.watch<FormBloc>().state;
if (blocWatch is RadioButtonFormState) {
value = blocWatch.buttonIndex;
}
return OutlinedButton(
onPressed: () {
formBloc.add(RadioButtonFormEvent(
buttonIndex: index,
buttonName: text,
));
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
side: BorderSide(color: (value == index) ? Colors.blue : Colors.grey),
),
child: Text(
text,
style: TextStyle(
color: (value == index) ? Colors.blue : Colors.grey,
),
));
}
#override
Widget build(BuildContext context) {
return Row(
children: [
CustomRadioButton("option one", 0, context),
const SizedBox(width: 15),
CustomRadioButton("option two", 1, context),
],
);
}
}
Your FormInput class should be extends from StatefulWidget, not StatelessWidget.
After this change, you should remove all TextEditingController assignments from the build() method and move them into initState().

Flutter Riverpod redirection after entity creation

I'm working on my Flutter app and I try to set up a redirection process after creating an entity (user). The state management is handled by Riverpod. I'm using Firebase for the auth and Postgres for the database.
The insert method in the repository returns a User. With a StateNotifier I just want to check if the method returns a user. If the user is returned I set a success state object (CreateAccountStateSuccess), if it's not I set an error state object with a message. Problem: my saveUser method always return null in my StateNotifier, even though my user is persisted in Firebase and my database. I think it's a Riverpod issue. Any idea?
My repository:
Future<AppUser?> saveUser(String email, String nickname, String role,
String firstname, String lastname) async {
try {
connection.open().then((value) async {
Future<List<Map<String, Map<String, dynamic>>>> result = connection.mappedResultsQuery(
'insert into public.user(email,nickname,role,firstname,lastname) '
'values(#emailValue,#nicknameValue,#roleValue,#firstnameValue,#lastnameValue) '
'returning *',
substitutionValues: {
'emailValue': email,
'nicknameValue': nickname,
'roleValue': role,
'firstnameValue': firstname,
'lastnameValue': lastname,
},
allowReuse: true,
timeoutInSeconds: 30,
);
result.then((value) {
final userFromDataBase = value[0]['user']!;
return AppUser(
email: userFromDataBase['email'],
nickname: userFromDataBase['nickname'],
role: userFromDataBase['role'],
firstname: userFromDataBase['firstname'],
lastname: userFromDataBase['lastname']
);
});
});
} catch (e) {
print(ErrorHandler(message: e.toString()));
return null;
}
return null;
}
My Firebase method to create user for Firebase and using my repository method:
Future<AppUser?> registerWithEmailAndPassword(String email, String password, String nickname, String role, String firstname, String lastname) async {
FirebaseApp app = await Firebase.initializeApp(
name: 'Secondary', options: Firebase.app().options);
try {
UserCredential result =
await FirebaseAuth.instanceFor(app: app).createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
if (user == null) {
throw Exception("No user found");
} else {
try {
return await UserRepository(user.email!).saveUser(email, nickname, role, firstname, lastname);
} on PostgreSQLException catch (e) {
print('CATCH POSTGRES EXCEPTION');
print(ErrorHandler(message: e.code.toString()));
}
}
} on FirebaseException catch (e) {
print('CATCH FIREBASE EXCEPTION');
print(ErrorHandler(message: e.code.toString()));
}
return null;
}
My controller:
class CreateAccountController extends StateNotifier<CreateAccountState> {
CreateAccountController(this.ref) : super(const CreateAccountStateInitial());
final Ref ref;
void register(String email, String password, String nickname, String role, String firstname, String lastname) async {
state = const CreateAccountStateLoading();
try {
await ref.read(authRepositoryProvider).registerWithEmailAndPassword(
email,
password,
nickname,
role,
firstname,
lastname
).then((user){
user != null ? state = const CreateAccountStateSuccess() : state = const CreateAccountStateError('Something went wrong with the user creation in database');
});
} catch (e) {
state = CreateAccountStateError(e.toString());
}
}
}
final createAccountControllerProvider =
StateNotifierProvider<CreateAccountController, CreateAccountState>((ref) {
return CreateAccountController(ref);
});
My state objects :
class CreateAccountState extends Equatable {
const CreateAccountState();
#override
List<Object> get props => [];
}
class CreateAccountStateInitial extends CreateAccountState {
const CreateAccountStateInitial();
#override
List<Object> get props => [];
}
class CreateAccountStateLoading extends CreateAccountState {
const CreateAccountStateLoading();
#override
List<Object> get props => [];
}
class CreateAccountStateSuccess extends CreateAccountState {
const CreateAccountStateSuccess();
#override
List<Object> get props => [];
}
class CreateAccountStateError extends CreateAccountState {
final String error;
const CreateAccountStateError(this.error);
#override
List<Object> get props => [error];
}
My screen:
class CreateAccountScreen extends StatefulHookConsumerWidget {
const CreateAccountScreen({Key? key}) : super(key: key);
#override
ConsumerState<CreateAccountScreen> createState() => _CreateAccountScreenState();
}
class _CreateAccountScreenState extends ConsumerState<CreateAccountScreen> {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController nicknameController = TextEditingController();
TextEditingController roleController = TextEditingController();
TextEditingController firstnameController = TextEditingController();
TextEditingController lastnameController = TextEditingController();
#override
Widget build(BuildContext context) {
ref.listen<CreateAccountState>(createAccountControllerProvider, ((previous, state) {
if (state is CreateAccountStateError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.error.toString()),
backgroundColor: Colors.red,
));
}
print(state.toString());
if (state is CreateAccountStateSuccess) {
context.goNamed('/', params:
{
'screenName': 'users'
});
}
}));
return Scaffold(
appBar: AppBar(
title: const Text('Create an account'),
elevation: 8.0,
backgroundColor: Colors.deepOrangeAccent,
actions: [
TextButton.icon(
icon: const Icon(
Icons.logout_rounded,
color: Colors.white,
),
label: const Text('', style: TextStyle(color: Colors.white)),
onPressed: () async {
ref.read(loginControllerProvider.notifier).signOut();
},
),
]
),
body: Padding(
padding: const EdgeInsets.all(10),
child: ListView(
children: <Widget>[
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(10),
child: const Text(
'Ludocal 2',
style: TextStyle(
color: Colors.deepOrange,
fontWeight: FontWeight.w500,
fontSize: 30),
)),
Container(
padding: const EdgeInsets.all(10),
child: TextFormField(
validator: (value) =>
value == null || value.isEmpty ? "Enter an email" : null,
controller: emailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email Address',
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: TextFormField(
obscureText: true,
validator: (value) =>
value == null || value.isEmpty ? "Enter a password" : null,
controller: passwordController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: TextFormField(
validator: (value) =>
value == null || value.isEmpty ? "Enter a nickname" : null,
controller: nicknameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Nickname',
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: TextFormField(
validator: (value) =>
value == null || value.isEmpty ? "Enter a role" : null,
controller: roleController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Role',
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: TextFormField(
validator: (value) =>
value == null || value.isEmpty ? "Enter a firstname" : null,
controller: firstnameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Firstname',
),
),
),
Container(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
child: TextFormField(
validator: (value) =>
value == null || value.isEmpty ? "Enter a lastname" : null,
controller: lastnameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Lastname',
),
),
),
Container(
height: 50,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
margin: const EdgeInsets.only(top:20),
child: ElevatedButton(
child: const Text('Create', style: TextStyle(color: Colors.white)),
onPressed: () {
ref
.read(createAccountControllerProvider.notifier)
.register(emailController.text, passwordController.text, nicknameController.text,
roleController.text, firstnameController.text, lastnameController.text);
},
)),
],
)),
);
}
}
The problem is in the saveUser function. Instead of using .then use await. It would be like the following:
Future<AppUser?> saveUser(String email, String nickname, String role,
String firstname, String lastname) async {
try {
await connection.open();
final result = await connection.mappedResultsQuery(
'insert into public.user(email,nickname,role,firstname,lastname) '
'values(#emailValue,#nicknameValue,#roleValue,#firstnameValue,#lastnameValue) '
'returning *',
substitutionValues: {
'emailValue': email,
'nicknameValue': nickname,
'roleValue': role,
'firstnameValue': firstname,
'lastnameValue': lastname,
},
allowReuse: true,
timeoutInSeconds: 30,
);
final userFromDataBase = result[0]['user']!;
return AppUser(
email: userFromDataBase['email'],
nickname: userFromDataBase['nickname'],
role: userFromDataBase['role'],
firstname: userFromDataBase['firstname'],
lastname: userFromDataBase['lastname'],
);
} catch (e) {
print(ErrorHandler(message: e.toString()));
return null;
}
}

How do i make a user input a picture in a list view and then save the image to firebase in flutter

I created a form in which a user would fill and the information would be sent to firebase for storage and then retrieve in a later time
Here is the code I wrote for it.
PS: I have not written anything about the image yet
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class AddItem extends StatelessWidget {
const AddItem({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final nameController = TextEditingController();
final quantityController = TextEditingController();
final numberController = TextEditingController();
final priceController = TextEditingController();
final addressController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text("Add Items"),
centerTitle: true,
),
body: ListView(
padding: EdgeInsets.all(16),
children: <Widget>[
TextField(
autocorrect: true,
controller: nameController,
decoration: decoration("Name of Item"),
),
SizedBox(
height: 24,
),
TextField(
controller: quantityController,
decoration: decoration("Quantity Available"),
keyboardType: TextInputType.number,
),
SizedBox(
height: 24,
),
TextField(
controller: numberController,
decoration: decoration("Phone Number of Seller"),
keyboardType: TextInputType.number,
),
SizedBox(
height: 24,
),
TextField(
controller: priceController,
decoration: decoration("Price of Item (in Naira)"),
keyboardType: TextInputType.number,
),
SizedBox(
height: 24,
),
TextField(
controller: addressController,
decoration: decoration("Address of Seller"),
keyboardType: TextInputType.streetAddress,
),
SizedBox(
height: 24,
),
ElevatedButton(
onPressed: () {
final item = SellerItem(
name: nameController.text,
quantity: int.parse(quantityController.text),
number: int.parse(numberController.text),
price: int.parse(priceController.text),
address: addressController.text
);
createItem(item);
Navigator.pop(context);
},
child: Text("Add Item"))
],
),
);
}
InputDecoration decoration(String label) => InputDecoration(
labelText: label,
border: OutlineInputBorder(),
);
Future createItem(SellerItem item) async {
final docUser = FirebaseFirestore.instance.collection('Items').doc();
item.id = docUser.id;
final json = item.toJson();
await docUser.set(json);
}
}
class SellerItem {
String id;
final String name;
final int quantity;
final int number;
final int price;
final String address;
SellerItem({
this.id = '',
required this.name,
required this.quantity,
required this.number,
required this.price,
required this.address
});
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'quantity': quantity,
'number': number,
'price': price,
'address': address
};
}
I want to add an image I can retrieve in the SellerItem class.
I would need help and more explanations.
Thanks a lot

My objective is to put message response everytime user enter the same email into the snackbar but what i get is a null

ive been getting this error for days now but still cant figure it out. My objective is to put message response everytime user enter the same email into the snackbar but what i get is a null.
This is my signup screen.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:form_field_validator/form_field_validator.dart';
import 'package:formvalid/models/register_model.dart';
import 'package:http/http.dart' as http;
import 'package:formvalid/progressHUD.dart';
import 'package:formvalid/screens/login.dart';
class SignupScreen extends StatefulWidget {
const SignupScreen({Key? key}) : super(key: key);
#override
_SignupScreenState createState() => _SignupScreenState();
}
class RegisterAPI {
Future<RegisterResponse> register(
String name, String email, String password) async {
String url = 'http://api.staging.tarsoft.co/api/register';
final response = await http.post(Uri.parse('$url'),
body: {'name': name, 'email': email, 'password': password});
if (response.statusCode == 200 || response.statusCode == 401) {
print(response.body);
// print(response.statusCode);
return RegisterResponse.fromJson(json.decode(response.body));
} else {
print(response.body);
throw Exception('Failed to load data');
}
}
}
class _SignupScreenState extends State<SignupScreen> {
TextEditingController passController = new TextEditingController();
TextEditingController emailController = new TextEditingController();
TextEditingController nameController = new TextEditingController();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
final scaffoldKey = GlobalKey<FormState>();
late RegisterResponse _user;
bool hidePassword = true;
bool isApiCallprocess = false;
#override
void initState() {
super.initState();
_user = new RegisterResponse();
}
#override
Widget build(BuildContext context) {
return ProgressHUD(
child: _uibuild(context),
inAsyncCall: isApiCallprocess,
opacity: 0.3,
);
}
Widget _uibuild(BuildContext context) {
final double height = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
key: scaffoldKey,
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => LoginScreen()));
},
)),
backgroundColor: Color(0xFFffffff),
body: SingleChildScrollView(
child: Form(
key: formKey,
child: Container(
padding: EdgeInsets.only(left: 40, right: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: height * 0.04),
Text(
'Prizes and Suprises',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color(0xFF363f93)),
),
Text(
'Await you !',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color(0xFF363f93)),
),
SizedBox(
height: height * 0.03,
),
TextFormField(
controller: nameController,
onSaved: (input) => _user.name = input,
decoration: InputDecoration(labelText: 'Enter your name'),
validator: (value) {
if (value!.isEmpty ||
!RegExp(r'^[a-z A-Z]+$').hasMatch(value)) {
return 'Enter correct name';
} else {
return null;
}
}),
SizedBox(height: height * 0.05),
TextFormField(
controller: emailController,
onSaved: (input) => _user.email = input,
decoration: InputDecoration(labelText: 'Enter your email'),
validator: MultiValidator([
RequiredValidator(errorText: 'Enter your email'),
EmailValidator(errorText: 'Not A Valid Email')
])),
SizedBox(height: height * 0.05),
TextFormField(
controller: passController,
onSaved: (input) => _user.password = input,
decoration: InputDecoration(labelText: 'Enter your password'),
validator: MultiValidator([
RequiredValidator(errorText: 'Enter your password'),
MinLengthValidator(6,
errorText: 'Should be at least 6 characters')
]),
obscureText: hidePassword,
),
SizedBox(height: height * 0.05),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
final String name = nameController.text;
final String email = emailController.text;
final String password = passController.text;
if (validateAndSave()) {
setState(() {
isApiCallprocess = true;
});
RegisterAPI registerAPI = new RegisterAPI();
registerAPI
.register(name, email, password)
.then((value) => {
setState(() {
isApiCallprocess = false;
}),
if (value.token?.isNotEmpty ?? false)
{
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(
'${_user.name}')))
}
else
{
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content:
Text('${_user.message}')))
}
});
print(_user.toJson());
}
},
child: Container(
margin: EdgeInsets.only(left: 100.0),
child: Padding(
padding: const EdgeInsets.only(left: 35, top: 10),
child: Text(
'Register',
style: TextStyle(color: Colors.white),
),
),
height: 40,
width: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Color(0xFF363f93),
),
)),
SizedBox(
height: height * 0.09,
),
],
),
],
),
),
),
),
);
}
bool validateAndSave() {
final form = formKey.currentState;
if (form!.validate()) {
form.save();
return true;
}
return false;
}
}
//ignore: must_be_immutable
class TextInput extends StatelessWidget {
final String textString;
TextEditingController textController;
final bool obscureText;
TextInput(
{Key? key,
required this.textString,
required this.textController,
required this.obscureText})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextField(
style: TextStyle(color: Color(0xFF000000)),
cursorColor: Color(0xFF9b9b9b),
controller: textController,
keyboardType: TextInputType.text,
obscureText: obscureText,
decoration: InputDecoration(
hintText: this.textString,
hintStyle: TextStyle(
color: Color(0xFF9b9b9b),
fontSize: 15,
fontWeight: FontWeight.normal),
),
);
}
}
This is my responseregister
class RegisterResponse{
String? email;
String? password;
String? name;
String? token;
String? error;
String? message;
String? success;
RegisterResponse({this.email, this.password, this.name,this.token,this.error,this.message,this.success});
factory RegisterResponse.fromJson(Map<String, dynamic> json) =>
RegisterResponse(
name: json['name'],
email: json['email'],
password: json['password'],
token: json['token'] == null ? json['token'] : '',
error: json['error'],
message: json['email'],
success: json['Success']
);
Map<String, dynamic> toJson() {
Map<String, dynamic> map = {
'name': name,
'email': email,
'password': password,
};
return map;
}
}
this is in debugger console shows "the email has already been taken"
display null

how to create resuable textfield with validator in flutter

I am creating a login form which has username and password field, i want to add validation when user skip any field. I have created this reusable textfield.
class RoundedInputField extends StatelessWidget {
final String hintText;
final ValueChanged<String> onChanged;
final TextEditingController controller;
final FormFieldValidator validate;
const RoundedInputField({Key key, this.hintText,
this.onChanged,
this.controller,this.validate,
})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextFieldContainer(
child: TextFormField(
onChanged: onChanged,
controller: TextEditingController(),
validator: validate,
decoration: InputDecoration(
hintText: hintText,
border: InputBorder.none,
),
),
);
}
}
and calling it like this
RoundedInputField(hintText: "Username",icon: Icons.email,fontsize: 20,
controller: TextEditingController(text: user.username),
onChanged: (value){
user.username=value;
},
validate: (value){
if(value.isEmpty){
return "This field is required";
}
},
),
but validator property is not working properly, here it is.
please help if anyone know how to fix it!
I have been using TextFormField in a reusable way like this , it has served me for all the purposes i needed , i think it works for your case too
class BoxTextField extends StatelessWidget {
final TextEditingController controller;
final FormFieldValidator<String> validator;
final bool obsecure;
final bool readOnly;
final VoidCallback onTap;
final VoidCallback onEditingCompleted;
final TextInputType keyboardType;
final ValueChanged<String> onChanged;
final bool isMulti;
final bool autofocus;
final bool enabled;
final String errorText;
final String label;
final Widget suffix;
final Widget prefix;
BoxTextField(
{Key key,
this.controller,
this.validator,
this.keyboardType = TextInputType.text,
this.obsecure = false,
this.onTap,
this.isMulti = false,
this.readOnly = false,
this.autofocus = false,
this.errorText,
#required this.label,
this.suffix,
this.prefix,
this.enabled = true,
this.onEditingCompleted,
this.onChanged})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: TextFormField(
onChanged: onChanged,
onEditingComplete: onEditingCompleted,
autofocus: autofocus,
minLines: isMulti ? 4 : 1,
maxLines: isMulti ? null : 1,
onTap: onTap,
enabled: enabled,
readOnly: readOnly,
obscureText: obsecure,
keyboardType: keyboardType,
controller: controller,
decoration: InputDecoration(
errorText: errorText,
prefixIcon: prefix,
suffixIcon: suffix,
labelStyle: TextStyle(fontSize: lableFontSize()),
labelText: label,
hintStyle: TextStyle(color: Colors.blueGrey, fontSize: 15),
contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 20),
enabledBorder: textFieldfocused(),
border: textFieldfocused(),
focusedBorder: textFieldfocused(),
errorBorder: errorrTextFieldBorder(),
focusedErrorBorder: errorrTextFieldBorder(),
),
validator: validator),
);
}
}
This is the Usage
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
TextEditingController _emailPhone = new TextEditingController();
TextEditingController _password = new TextEditingController();
GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
#override
void initState() {
super.initState();
}
String loginError;
bool loggingIn = false;
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 50,
),
BoxTextField(
validator: (str) {
if (str.isEmpty) {
return tr('common.required');
}
return null;
},
controller: _emailPhone,
label: tr('login.username'),
),
SizedBox(
height: 10,
),
BoxTextField(
label: tr('login.password'),
controller: _password,
obsecure: true,
validator: (str) {
if (str.isEmpty) {
return tr('common.required');
}
return null;
},
),
Center(
child: BoxButton(
loading: loggingIn,
lable: tr('login.btn'),
onPressed: () {
},
)),
],
)),
)
]))
],
);
}
}
Replace your code and try this:
RoundedInputField(
hintText: "Username",
icon: Icons.email,
fontsize: 20,
controller: TextEditingController(text: user.username),
onChanged: (value) {
user.username = value;
},
validate: (value) {
if (value.isEmpty) {
return "This field is required";
}
return null;
},
),