StateProvider not updating with correct values - flutter

I've been trying to use riverpod for state management in a POC app, I have a screen with a text editing controller, I'm trying to check whether the text is empty for a textfield and enable/disable a button based on that logic. There seems to be an issue with my code since the button seems to always display the opposite status of what I'm trying to do. How can I fix this issue ?
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:to_do_list/models/task.dart';
import 'package:to_do_list/reusable_widgets/app_bar.dart';
class TaskScreen extends ConsumerStatefulWidget {
const TaskScreen({Key? key}) : super(key: key);
#override
TaskScreenState createState() => TaskScreenState();
}
class TaskScreenState extends ConsumerState<TaskScreen> {
DateTime? _selectedDate;
static final TextEditingController _titleController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
var textProvider = StateProvider((_) => _titleController.text.isNotEmpty);
#override
Widget build(BuildContext context) {
bool value = ref.watch(textProvider);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: const AppBarWrapper(
title: "To-Do List",
),
body: Stack(children: [
Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
children: [
TextField(
controller: _titleController,
onChanged: (val) {
ref.read(textProvider.state).state = val.isEmpty;
},
autofocus: true,
decoration: const InputDecoration(hintText: "Title"),
),
const SizedBox(
height: 14,
),
TextField(
controller: _descriptionController,
decoration: const InputDecoration(hintText: "Description"),
minLines: 3,
maxLines: 5,
),
SizedBox(
width: 150,
child: ListTile(
leading: const Icon(Icons.calendar_today),
title: Text(
_selectedDate?.toString() ?? 'No Date',
),
onTap: () async {
_selectedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
},
),
),
],
),
),
Positioned(
bottom: MediaQuery.of(context).viewInsets.bottom,
left: 0,
right: 0,
child: ElevatedButton(
onPressed: value
? () {
Navigator.of(context).pop(Task(
title: _titleController.text,
description: _descriptionController.text.isNotEmpty
? _descriptionController.text
: null,
date: _selectedDate));
}
: null,
child: const Icon(
Icons.done,
),
),
),
]),
);
}
}

Try to change ref.read(textProvider.state).state = val.isEmpty; to ref.read(textProvider.notifier).state = val.isEmpty;. It should help you

Related

UI changes only when I hot reload the app

I'm trying to get data from bottomsheet to model then I show them in listview.
I can see everything works but when I trying to add new data it does not show first and then if I hot reload the app I can add data and see it listed but for new data I must hot reload again and again I couldn't find the solution
bool? isChecked = false;
final boy = TextEditingController();
final adet = TextEditingController();
final kilo = TextEditingController();
final firma = TextEditingController();
final kalit = TextEditingController();
final kalinlik = TextEditingController();
final en = TextEditingController();
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
Widget build(BuildContext context, WidgetRef ref) {
var size = MediaQuery.of(context).size;
final productInfoProvider = ref.watch(productProvider);
ProductInfos product1 = ProductInfos(
firmAdi: firma.text,
kalite: kalit.text,
kalinlik: kalinlik.text,
en: en.text,
boy: boy.text,
adet: adet.text,
kilo: kilo.text,
pvc: isChecked);
ProductInfos product2 = ProductInfos(
firmAdi: "firma.text",
kalite: "kalit.text",
kalinlik: "kalinlik.text",
en: "en.text",
boy: "boy.text",
adet: "adet.text",
kilo: "kilo.text",
pvc: true);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
SizedBox(
height: size.height * 0.5,
child: ListView.separated(
separatorBuilder: (context, index) {
return const Divider(
height: 5,
thickness: 2,
);
},
itemCount: productInfoProvider.products.length,
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.white,
child: ListTile(
leading: Text(productInfoProvider
.products[index].firmAdi
.toString()),
subtitle: Text(
productInfoProvider.products[index].en.toString()),
title: Text(
"${productInfoProvider.products[index].kalite.toString()} kalite"),
),
);
},
)),
Center(
child: SizedBox(
height: size.height * 0.2,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black),
onPressed: () {
print(productInfoProvider.products.length);
showModalBottomSheet(
context: context,
builder: ((context) {
return StatefulBuilder(
//bottomSheet de statefulbuilder kullanmazsak state yenilenmiyor
builder: (BuildContext context,
StateSetter myState) {
return SizedBox(
height: size.height * 0.5,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
solBottomSheet(size, myState),
sagBottomSheet(size, myState),
],
),
Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: (() {
myState(() {
ref
.read(productProvider)
.addProduct(product1);
inspect(productInfoProvider
.products);
});
}),
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xff74a6cc)),
child: const Text("ekle"),
)),
],
),
),
);
});
}));
},
child: const Text("BottomSheet"),
)),
),
],
));
}
SizedBox sagBottomSheet(Size size, StateSetter myState) {
return SizedBox(
width: size.width * 0.4,
child: Column(
children: [
CustomTextField(
textController: boy,
t: TextInputType.number,
hintText: "Boy",
ic: const Icon(Icons.numbers_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
space(),
CustomTextField(
textController: adet,
t: TextInputType.number,
hintText: "Adet",
ic: const Icon(Icons.numbers_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
space(),
CustomTextField(
textController: kilo,
t: TextInputType.number,
hintText: "Kilo",
ic: const Icon(Icons.numbers_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
space(),
CheckboxListTile(
title: const Text(
"PVC",
style: TextStyle(
color: Color(0xff74a6cc), fontWeight: FontWeight.w800),
),
value: isChecked,
onChanged: ((value) {
myState(
() {
isChecked = value;
},
);
})),
],
));
}
SizedBox solBottomSheet(Size size, StateSetter myState) {
return SizedBox(
width: size.width * 0.4,
child: Column(
children: [
CustomTextField(
textController: firma,
t: TextInputType.name,
hintText: "Firma",
ic: const Icon(Icons.home),
),
space(),
CustomTextField(
textController: kalit,
t: TextInputType.number,
hintText: "Kalite",
ic: const Icon(Icons.high_quality_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
space(),
CustomTextField(
textController: kalinlik,
t: TextInputType.number,
hintText: "Kalınlık",
ic: const Icon(Icons.high_quality_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
space(),
CustomTextField(
textController: en,
t: TextInputType.number,
hintText: "En",
ic: const Icon(Icons.numbers_outlined),
dgonly: [FilteringTextInputFormatter.digitsOnly],
),
],
),
);
}
SizedBox space() {
return const SizedBox(
height: 10,
);
}
}
//this is provider code
class ProductInfoRepo extends ChangeNotifier {
List<ProductInfos> products = [
ProductInfos(
firmAdi: "firm1",
kalite: "430",
kalinlik: "0.50",
en: "750",
boy: "1000",
adet: "500",
kilo: "1500",
pvc: true),
];
addProduct(ProductInfos product) {
products.add(product);
notifyListeners();
}
}
final productProvider = ChangeNotifierProvider((((ref) {
return ProductInfoRepo();
})));
I modified your code a little bit, and it worked.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() => runApp(const ProviderScope(
child: MaterialApp(home: MyHomePage(title: 'MyHomePage'))));
class MyHomePage extends ConsumerStatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
ConsumerState<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends ConsumerState<MyHomePage> {
bool isChecked = false;
final boy = TextEditingController();
final adet = TextEditingController();
final kilo = TextEditingController();
final firma = TextEditingController();
final kalit = TextEditingController();
final kalinlik = TextEditingController();
final en = TextEditingController();
#override
void dispose() {
boy.dispose();
adet.dispose();
kilo.dispose();
firma.dispose();
kalit.dispose();
kalinlik.dispose();
en.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final productInfoProvider = ref.watch(productProvider);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
SizedBox(
height: size.height * 0.5,
child: ListView.separated(
separatorBuilder: (context, index) {
return const Divider(
height: 5,
thickness: 2,
);
},
itemCount: productInfoProvider.products.length,
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.white,
child: ListTile(
leading: Text(
productInfoProvider.products[index].firmAdi.toString()),
subtitle:
Text(productInfoProvider.products[index].en.toString()),
title: Text(
"${productInfoProvider.products[index].kalite.toString()} kalite"),
),
);
},
),
),
Center(
child: SizedBox(
height: size.height * 0.2,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black),
onPressed: () {
print(productInfoProvider.products.length);
showModalBottomSheet(
context: context,
builder: ((context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter myState) {
return SizedBox(
height: size.height * 0.5,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
solBottomSheet(size, myState),
sagBottomSheet(size, myState),
],
),
Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: (() {
final newProduct = createNewProduct();
ref
.read(productProvider)
.addProduct(newProduct);
inspect(productInfoProvider.products);
}),
style: ElevatedButton.styleFrom(
backgroundColor:
const Color(0xff74a6cc)),
child: const Text("ekle"),
),
),
],
),
),
);
});
}),
);
},
child: const Text("BottomSheet"),
),
),
),
],
),
);
}
createNewProduct() {
return ProductInfos(
firmAdi: firma.text,
kalite: kalit.text,
kalinlik: kalinlik.text,
en: en.text,
boy: boy.text,
adet: adet.text,
kilo: kilo.text,
pvc: isChecked,
);
}
SizedBox sagBottomSheet(Size size, StateSetter myState) {
print('build sagBottomSheet');
return SizedBox(
width: size.width * 0.4,
child: Column(
children: [
CustomTextField(
textController: boy,
t: TextInputType.number,
hintText: "Boy",
ic: const Icon(Icons.numbers_outlined),
dgonly: true,
),
space(),
CustomTextField(
textController: adet,
t: TextInputType.number,
hintText: "Adet",
ic: const Icon(Icons.numbers_outlined),
dgonly: true,
),
space(),
CustomTextField(
textController: kilo,
t: TextInputType.number,
hintText: "Kilo",
ic: const Icon(Icons.numbers_outlined),
dgonly: true,
),
space(),
CheckboxListTile(
title: const Text(
"PVC",
style: TextStyle(
color: Color(0xff74a6cc), fontWeight: FontWeight.w800),
),
value: isChecked,
onChanged: ((value) {
myState(() {
isChecked = value ?? false;
});
}),
),
],
),
);
}
SizedBox solBottomSheet(Size size, StateSetter myState) {
return SizedBox(
width: size.width * 0.4,
child: Column(
children: [
CustomTextField(
textController: firma,
t: TextInputType.name,
hintText: "Firma",
ic: const Icon(Icons.home),
),
space(),
CustomTextField(
textController: kalit,
t: TextInputType.number,
hintText: "Kalite",
ic: const Icon(Icons.high_quality_outlined),
dgonly: true,
),
space(),
CustomTextField(
textController: kalinlik,
t: TextInputType.number,
hintText: "Kalınlık",
ic: const Icon(Icons.high_quality_outlined),
dgonly: true,
),
space(),
CustomTextField(
textController: en,
t: TextInputType.number,
hintText: "En",
ic: const Icon(Icons.numbers_outlined),
dgonly: true,
),
],
),
);
}
SizedBox space() {
return const SizedBox(
height: 10,
);
}
}
class CustomTextField extends ConsumerWidget {
const CustomTextField({
required this.textController,
required this.t,
required this.hintText,
required this.ic,
this.dgonly,
Key? key,
}) : super(key: key);
final TextEditingController textController;
final TextInputType t;
final String hintText;
final Icon ic;
final bool? dgonly;
#override
Widget build(BuildContext context, WidgetRef ref) {
return TextField(
decoration: InputDecoration(
hintText: hintText,
icon: ic,
),
controller: textController,
keyboardType: TextInputType.number,
inputFormatters:
dgonly ?? false ? [FilteringTextInputFormatter.digitsOnly] : null,
);
}
}
Models and provider like this:
final productProvider = ChangeNotifierProvider((ref) {
return ProductInfoRepo();
});
class ProductInfoRepo extends ChangeNotifier {
List<ProductInfos> products = [
ProductInfos(
firmAdi: "firm1",
kalite: "430",
kalinlik: "0.50",
en: "750",
boy: "1000",
adet: "500",
kilo: "1500",
pvc: true,
),
];
addProduct(ProductInfos product) {
products.add(product);
notifyListeners();
}
}
class ProductInfos {
ProductInfos({
required this.firmAdi,
required this.kalite,
required this.kalinlik,
required this.en,
required this.boy,
required this.adet,
required this.kilo,
required this.pvc,
});
final String firmAdi;
final String kalite;
final String kalinlik;
final String en;
final String boy;
final String adet;
final String kilo;
final bool pvc;
}
This example is fully functional, you can copy and run it on your device.
The basic idea behind the modifications was:
Creating a createNewProduct() function that creates a new object every time the user clicks the add button.
Also, you need to use ConsumerStatefulWidget to be able to recycle all TextEditingController(), otherwise it will cause a memory leak.
Bonus: use widget decomposition to make the code readable. It also helps to rearrange only the right elements during state changes.

Not able to remove focus from input field

I have four textfields, a title field, a details field, a date field, and a time field. Both the date and time fields are wrapped within a gesture detector, and onTap calls a pickDateAndTime method. The problem is that when I click on the date field and try to manually change the time through the input rather than the dial way, the focus goes to the title field and when I am still on the time picker and type something in the time picker, the title field gets changed with the new input. The weird part is that this error just appeared out of nowhere, and there are no errors reported in the console.
class TodoScreen extends StatefulWidget {
final int? todoIndex;
final int? arrayIndex;
const TodoScreen({Key? key, this.todoIndex, this.arrayIndex})
: super(key: key);
#override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final ArrayController arrayController = Get.find();
final AuthController authController = Get.find();
final String uid = Get.find<AuthController>().user!.uid;
late TextEditingController _dateController;
late TextEditingController _timeController;
late TextEditingController titleEditingController;
late TextEditingController detailEditingController;
late String _setTime, _setDate;
late String _hour, _minute, _time;
late String dateTime;
late bool done;
#override
void initState() {
super.initState();
String title = '';
String detail = '';
String date = '';
String? time = '';
if (widget.todoIndex != null) {
title = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].title ??
'';
detail = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].details ??
'';
date = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].date!;
time = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].time;
}
_dateController = TextEditingController(text: date);
_timeController = TextEditingController(text: time);
titleEditingController = TextEditingController(text: title);
detailEditingController = TextEditingController(text: detail);
done = (widget.todoIndex == null)
? false
: arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].done!;
}
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay(
hour: (TimeOfDay.now().minute > 55)
? TimeOfDay.now().hour + 1
: TimeOfDay.now().hour,
minute: (TimeOfDay.now().minute > 55) ? 0 : TimeOfDay.now().minute + 5);
Future<DateTime?> _selectDate() => showDatePicker(
builder: (context, child) {
return datePickerTheme(child);
},
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
initialDate: selectedDate,
initialDatePickerMode: DatePickerMode.day,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 5));
Future<TimeOfDay?> _selectTime() => showTimePicker(
builder: (context, child) {
return timePickerTheme(child);
},
context: context,
initialTime: selectedTime,
initialEntryMode: TimePickerEntryMode.input);
Future _pickDateTime() async {
DateTime? date = await _selectDate();
if (date == null) return;
if (date != null) {
selectedDate = date;
_dateController.text = DateFormat("MM/dd/yyyy").format(selectedDate);
}
TimeOfDay? time = await _selectTime();
if (time == null) {
_timeController.text = formatDate(
DateTime(
DateTime.now().year,
DateTime.now().day,
DateTime.now().month,
DateTime.now().hour,
DateTime.now().minute + 5),
[hh, ':', nn, " ", am]).toString();
}
if (time != null) {
selectedTime = time;
_hour = selectedTime.hour.toString();
_minute = selectedTime.minute.toString();
_time = '$_hour : $_minute';
_timeController.text = _time;
_timeController.text = formatDate(
DateTime(2019, 08, 1, selectedTime.hour, selectedTime.minute),
[hh, ':', nn, " ", am]).toString();
}
}
#override
Widget build(BuildContext context) {
bool visible =
(_dateController.text.isEmpty && _timeController.text.isEmpty)
? false
: true;
final formKey = GlobalKey<FormState>();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text((widget.todoIndex == null) ? 'New Task' : 'Edit Task',
style: menuTextStyle),
leadingWidth: (MediaQuery.of(context).size.width < 768) ? 90.0 : 100.0,
leading: Center(
child: Padding(
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.only(left: 0)
: const EdgeInsets.only(left: 21.0),
child: TextButton(
style: const ButtonStyle(
splashFactory: NoSplash.splashFactory,
),
onPressed: () {
Get.back();
},
child: Text(
"Cancel",
style: paragraphPrimary,
),
),
),
),
centerTitle: true,
actions: [
Center(
child: Padding(
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.only(left: 0)
: const EdgeInsets.only(right: 21.0),
child: TextButton(
style: const ButtonStyle(
splashFactory: NoSplash.splashFactory,
),
onPressed: () async {
},
child: Text((widget.todoIndex == null) ? 'Add' : 'Update',
style: paragraphPrimary),
),
),
)
],
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0)
: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 15.0),
child: Column(
children: [
Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
validator: Validator.titleValidator,
controller: titleEditingController,
autofocus: true, // problem here
autocorrect: false,
cursorColor: Colors.grey,
maxLines: 1,
maxLength: 25,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
counterStyle: counterTextStyle,
hintStyle: hintTextStyle,
hintText: "Title",
border: InputBorder.none),
style: todoScreenStyle),
primaryDivider,
TextField(
controller: detailEditingController,
maxLines: null,
autocorrect: false,
cursorColor: Colors.grey,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
counterStyle: counterTextStyle,
hintStyle: hintTextStyle,
hintText: "Notes",
border: InputBorder.none),
style: todoScreenDetailsStyle),
],
),
),
Visibility(
visible: (widget.todoIndex != null) ? true : false,
child: GestureDetector(
onTap: () {},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Completed",
style: todoScreenStyle,
),
Transform.scale(
scale: 1.3,
child: Theme(
data: ThemeData(
unselectedWidgetColor: const Color.fromARGB(
255, 187, 187, 187)),
child: Checkbox(
shape: const CircleBorder(),
checkColor: Colors.white,
activeColor: primaryColor,
value: done,
side: Theme.of(context).checkboxTheme.side,
onChanged: (value) {
setState(() {
done = value!;
});
})),
)
],
),
),
),
GestureDetector(
onTap: () async {
await _pickDateTime();
setState(() {
visible = true;
});
},
child: Column(
children: [
Row(
children: [
Flexible(
child: TextField(
enabled: false,
controller: _dateController,
onChanged: (String val) {
_setDate = val;
},
decoration: InputDecoration(
hintText: "Date",
hintStyle: hintTextStyle,
border: InputBorder.none),
style: todoScreenStyle,
),
),
visible
? IconButton(
onPressed: () {
_dateController.clear();
_timeController.clear();
setState(() {});
},
icon: const Icon(
Icons.close,
color: Colors.white,
))
: Container()
],
),
primaryDivider,
TextField(
onChanged: (String val) {
_setTime = val;
},
enabled: false,
controller: _timeController,
decoration: InputDecoration(
hintText: "Time",
hintStyle: hintTextStyle,
border: InputBorder.none),
style: todoScreenStyle,
)
],
),
),
],
),
),
),
);
}
}
Should I open an issue on Github, as I had not made any changes to the code for it behave this way and also because there were no errors in the console
Here is the full code on Github
Update
Here is a reproducible example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TodoScreen(),
);
}
}
class TodoScreen extends StatefulWidget {
const TodoScreen({Key? key}) : super(key: key);
#override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
late TextEditingController _dateController;
late TextEditingController _timeController;
late TextEditingController titleEditingController;
late TextEditingController detailEditingController;
late String _setTime, _setDate;
late String _hour, _minute, _time;
late String dateTime;
#override
void initState() {
super.initState();
String title = '';
String detail = '';
String date = '';
String? time = '';
_dateController = TextEditingController(text: date);
_timeController = TextEditingController(text: time);
titleEditingController = TextEditingController(text: title);
detailEditingController = TextEditingController(text: detail);
}
#override
void dispose() {
super.dispose();
titleEditingController.dispose();
detailEditingController.dispose();
_timeController.dispose();
_dateController.dispose();
}
Theme timePickerTheme(child) => Theme(
data: ThemeData.dark().copyWith(
timePickerTheme: TimePickerThemeData(
backgroundColor: const Color.fromARGB(255, 70, 70, 70),
dayPeriodTextColor: Colors.green,
hourMinuteTextColor: MaterialStateColor.resolveWith((states) =>
states.contains(MaterialState.selected)
? Colors.white
: Colors.white),
dialHandColor: Colors.green,
helpTextStyle: TextStyle(
fontSize: 12, fontWeight: FontWeight.bold, color: Colors.green),
dialTextColor: MaterialStateColor.resolveWith((states) =>
states.contains(MaterialState.selected)
? Colors.white
: Colors.white),
entryModeIconColor: Colors.green,
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor:
MaterialStateColor.resolveWith((states) => Colors.green)),
),
),
child: child!,
);
Theme datePickerTheme(child) => Theme(
data: ThemeData.dark().copyWith(
colorScheme: ColorScheme.dark(
surface: Colors.green,
secondary: Colors.green,
onPrimary: Colors.white,
onSurface: Colors.white,
primary: Colors.green,
)),
child: child!,
);
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay(
hour: (TimeOfDay.now().minute > 55)
? TimeOfDay.now().hour + 1
: TimeOfDay.now().hour,
minute: (TimeOfDay.now().minute > 55) ? 0 : TimeOfDay.now().minute + 5);
Future<DateTime?> _selectDate() => showDatePicker(
builder: (context, child) {
return datePickerTheme(child);
},
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
initialDate: selectedDate,
initialDatePickerMode: DatePickerMode.day,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 5));
Future<TimeOfDay?> _selectTime() => showTimePicker(
builder: (context, child) {
return timePickerTheme(child);
},
context: context,
initialTime: selectedTime,
initialEntryMode: TimePickerEntryMode.input);
Future _pickDateTime() async {
DateTime? date = await _selectDate();
if (date == null) return;
if (date != null) {
selectedDate = date;
_dateController.text = selectedDate.toString();
}
TimeOfDay? time = await _selectTime();
if (time != null) {
selectedTime = time;
_hour = selectedTime.hour.toString();
_minute = selectedTime.minute.toString();
_time = '$_hour : $_minute';
_timeController.text = _time;
_timeController.text =
DateTime(2019, 08, 1, selectedTime.hour, selectedTime.minute)
.toString();
}
}
#override
Widget build(BuildContext context) {
bool visible =
(_dateController.text.isEmpty && _timeController.text.isEmpty)
? false
: true;
final formKey = GlobalKey<FormState>();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
centerTitle: true,
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0)
: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 15.0),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14.0)),
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 15.0),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: titleEditingController,
autofocus: true,
autocorrect: false,
cursorColor: Colors.grey,
maxLines: 1,
maxLength: 25,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: "Title", border: InputBorder.none),
),
Divider(color: Colors.black),
TextField(
controller: detailEditingController,
maxLines: null,
autocorrect: false,
cursorColor: Colors.grey,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
hintText: "Notes", border: InputBorder.none),
),
],
),
)),
GestureDetector(
onTap: () async {
await _pickDateTime();
setState(() {
visible = true;
});
},
child: Container(
margin: const EdgeInsets.only(top: 20.0),
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 15.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.0)),
child: Column(
children: [
Row(
children: [
Flexible(
child: TextField(
enabled: false,
controller: _dateController,
onChanged: (String val) {
_setDate = val;
},
decoration: InputDecoration(
hintText: "Date", border: InputBorder.none),
),
),
visible
? IconButton(
onPressed: () {
_dateController.clear();
_timeController.clear();
setState(() {});
},
icon: const Icon(
Icons.close,
color: Colors.white,
))
: Container()
],
),
Divider(
color: Colors.blue,
),
TextField(
onChanged: (String val) {
_setTime = val;
},
enabled: false,
controller: _timeController,
decoration: InputDecoration(
hintText: "Enter", border: InputBorder.none),
)
],
)),
),
],
),
),
),
);
}
}
In your main.dart file, you should return something like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// This allows closing keyboard when tapping outside of a text field
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus!.unfocus();
}
},
child: // your app's entry point,
);
}
}
Add a focusNode in your textField:
FocusNode focusNode = FocusNode();
TextField(
focusNode: focusNode,
);
And then in the gesture detector, add that following code to unselect the textfield input:
FocusScope.of(context).requestFocus(FocusNode());
simply wrap your Scaffold widget GestureDetector and add FocusScope.of(context).requestFocus(FocusNode()); it will automatically unfocused text field when you click anywhere on your screen
GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Scaffold()
)
You can use below code to remove focus in gesture detector event
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
currentFocus.unfocus();
}

How to call setsate function from a different widget?

Well, I am coding a chatbot-like page in my app. But, I am stuck at calling setState function for page inside of chatBubble widget. Here is my page as MedicBot and chat question code as FirstQuestion. What I do want to do that whenever, user triggers radio tile's on tap condition. It should be trigger setState function in MedicBot, any suggestions?
import 'package:medicte/assets/back_button.dart';
import 'package:medicte/assets/first_question.dart';
class MedicBot extends StatefulWidget {
const MedicBot({Key? key}) : super(key: key);
#override
State<MedicBot> createState() => _MedicBotState();
}
class _MedicBotState extends State<MedicBot> {
late final List<Widget> _messages;
late final List<dynamic> botMessages;
FocusNode _focusNode = FocusNode();
setMainState() {
print('bum');
this.setState(() {});
}
#override
void initState() {
print('bumbeyarag');
botMessages = [
_buildChatBubbles(
widget: SizedBox.shrink(),
text:
'Do you have further medical information you can share? (e.g. lab results)',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: ['1-2 weeks', 'A Month', '1-3 Months', 'Other'],
setMainState: setMainState,
),
text: 'Where do you currently live?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: [
'Online Consultation',
'Second Opinion',
'A treatment cost',
'Other'
],
setMainState: setMainState,
),
text: 'How soon do you want to get the treatment done?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: ['Yes', 'No'],
setMainState: () {
setState(() {});
},
),
text: 'What service are you looking for?',
userControl: false),
_buildChatBubbles(
widget: FirstQuestion(
focus: _focusNode,
radioButtons: [],
setMainState: () {
setState(() {});
},
),
text: 'Have you already spoken a doctor?',
userControl: false),
_buildChatBubbles(
text: 'Which treatment are you interested in?',
userControl: false,
widget:
const Text('Enter a treatment name (e.g Hair Transplant, IVF)')),
_buildChatBubbles(
text: 'You are inquiring for',
userControl: false,
widget: FirstQuestion(
radioButtons: const ['Myself', 'For someone else'],
focus: _focusNode,
setMainState: () {
setState(() {});
},
)),
];
_messages = [
const SizedBox(
height: 1,
),
const SizedBox(
height: 10,
)
];
super.initState();
}
final TextEditingController _controller = TextEditingController();
bool value = false;
#override
Widget build(BuildContext context) {
if (botMessages.isNotEmpty) {
_messages.insert(1, botMessages.removeLast());
}
return Scaffold(
bottomSheet: Container(
color: Colors.white30,
child: Padding(
padding: const EdgeInsets.only(bottom: 30, right: 15, left: 15),
child: TextFormField(
focusNode: _focusNode,
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
),
hintText: 'Type your message',
suffixIcon: IconButton(
onPressed: () {
print(_controller.text);
print(_controller.value);
setState(() {
_messages.insert(
1,
_buildChatBubbles(
text: _controller.text,
userControl: true,
widget: const SizedBox.shrink()));
_controller.clear();
});
},
icon: const Icon(Icons.send),
),
),
),
),
),
appBar: AppBar(
leadingWidth: 101,
backgroundColor: Colors.blue.shade300,
leading: Row(
children: [
const BackWardButton(),
ClipRRect(
borderRadius: BorderRadius.circular(1000),
child: Container(
color: Colors.white,
child: Image.asset(
'lib/images/Lovepik_com-401792159-medical-robot.png',
height: 53,
width: 53),
),
),
],
),
title: const Text(
"MedicBot",
style: TextStyle(color: Colors.black54),
),
),
body: SafeArea(
minimum:
const EdgeInsets.only(top: 2, left: 10, right: 10, bottom: 90),
child: ListView.builder(
itemCount: _messages.length,
reverse: true,
itemBuilder: ((context, index) {
return _messages[index];
}),
)));
}
}
class _buildChatBubbles extends StatelessWidget {
bool userControl;
String text;
Widget widget;
_buildChatBubbles(
{required this.widget,
required this.text,
required this.userControl,
super.key});
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment:
userControl ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
userControl
? const SizedBox.shrink()
: Container(
margin: const EdgeInsets.only(right: 10),
child: const CircleAvatar(
radius: 20,
backgroundImage: AssetImage(
'lib/images/Lovepik_com-401792159-medical-robot.png'),
),
),
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4,
maxWidth: MediaQuery.of(context).size.width * 0.6),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: userControl ? Colors.green.shade300 : Colors.blue.shade300,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userControl ? 'You' : 'Medicte Bot',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 5),
Flexible(
child: Text(
text,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
widget
],
),
),
],
),
);
;
}
}
import 'package:flutter/material.dart';
import 'package:group_button/group_button.dart';
import 'package:medicte/pages/chat_ui.dart';
// ignore: must_be_immutable
class FirstQuestion extends StatefulWidget {
List<String> radioButtons;
FocusNode focus;
void Function() setMainState;
FirstQuestion(
{required this.setMainState,
required this.focus,
required this.radioButtons,
Key? key})
: super(key: key);
#override
State<FirstQuestion> createState() => _FirstQuestionState();
}
class _FirstQuestionState extends State<FirstQuestion> {
late GroupButtonController _radioController;
// ignore: prefer_typing_uninitialized_variables
late final _radioButtons;
#override
void initState() {
_radioButtons = widget.radioButtons;
_radioController = GroupButtonController(
selectedIndexes: [0, 1, 2, 3],
);
super.initState();
}
#override
Widget build(BuildContext context) {
return GroupButton(
controller: _radioController,
isRadio: true,
options: const GroupButtonOptions(groupingType: GroupingType.column),
buttons: _radioButtons,
buttonIndexedBuilder: (selected, index, context) {
return RadioTile(
title: _radioButtons[index],
selected: _radioController.selectedIndex,
index: index,
onTap: () {
print(_radioButtons[index].toString());
widget.setMainState();
_radioController.selectIndex(index);
/* Future.delayed(Duration(seconds: 1), () {
widget.setMainState();
}); */
},
);
},
onSelected: (val, i, selected) {
print('object');
});
}
}
class RadioTile extends StatelessWidget {
const RadioTile({
Key? key,
required this.selected,
required this.onTap,
required this.index,
required this.title,
}) : super(key: key);
final String title;
final int index;
final int? selected;
final VoidCallback onTap;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
onTap: onTap,
leading: Radio<int>(
groupValue: selected,
value: index,
onChanged: (val) {
print(val);
onTap();
},
),
);
}
}
Try something like this. This is the code snippet of an application of mine. I used StatefulBuilder as the parent of the widgets I want to update and I sent the setState parameter to the widget where I trigger.
import 'package:flutter/material.dart';
class CryptoassetsPage extends StatefulWidget {
const CryptoassetsPage({Key? key}) : super(key: key);
#override
_CryptoassetsPageState createState() => _CryptoassetsPageState();
}
class _CryptoassetsPageState extends State<CryptoassetsPage> {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
child: SingleChildScrollView(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
//My other class/widget
return OrderOptions(setState);
}),
),
);
}
}
class OrderOptions extends StatefulWidget {
const OrderOptions(this.setState, {Key? key}) : super(key: key);
final StateSetter setState;
#override
_OrderOptionsState createState() => _OrderOptionsState();
}
class _OrderOptionsState extends State<OrderOptions> {
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
StateSetter setState = widget.setState;
setState(() {});
},
);
}
}

Main page data is resetting automatically Flutter after user come back to second page to first page

I'm stuck with one issue where once I click on the upload image button it will take me to the next page wherein upload image class I will choose the images from the camera or Gallery and I will click on done. In the done button I have written navigation. pop(context) to the first page but when I come back all the text field data is cleared whatever I have entered and again I need to enter it back which is quite frustrating all the time...PLease help me to solve the issue or show the way how to save the data?
import '../blocs/addtrip_bloc.dart';
import '../widgets/customtextformfield.dart';
import 'CameraWidgetState.dart';
class DataInfoPage extends StatefulWidget {
const DataInfoPage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<DataInfoPage> {
_DataInfoPageState createState() => _DataInfoPageState();
String date = "";
final FirebaseAuth _auth = FirebaseAuth.instance;
User? user;
var name = '';
#override
void initState() {
super.initState();
initUser();
savedata();
}
initUser() async {
user = (await _auth.currentUser!);
name = user!.displayName!;
setState(() {});
}
void savedata() {
final absavedata = context.read<AddTripBloc>();
}
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
DateTime selectedDate = DateTime.now();
TextEditingController tripname = new TextEditingController();
TextEditingController sdate = new TextEditingController();
DateTimeRange? _selectedDateRange;
String _displayText(String begin, DateTime? date) {
if (date != null) {
return '$begin Date: ${date.toString().split(' ')[0]}';
} else {
return 'Press the button to show the picker';
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
SizedBox(
height: 20,
),
Text(
"Please enter trip details ",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 25,
color: Colors.black),
),
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 10, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
customfields(
hintValue: ' Enter Trip Name',
labeltxt: 'Trip Name *',
keyboardtype: TextInputType.text,
text: tripname),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FloatingActionButton(
onPressed: _show,
child: const Icon(Icons.date_range),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(5))),
Column(
children: [
Text(
_displayText(
'Start', _selectedDateRange?.start),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.black),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () async {
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CameraWidget()));
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => CameraWidget()));
},
child: const Text("Upload Images of Trip *"),
),
SizedBox(
height: 35,
),
],
)),
],
),
),
));
}
void _show() async {
final DateTimeRange? result = await showDateRangePicker(
context: context,
firstDate: DateTime(2022, 1, 1),
lastDate: DateTime(2030, 12, 31),
currentDate: DateTime.now(),
saveText: 'Done.',
);
if (result != null) {
// Rebuild the UI
print(result.start.toString());
setState(() {
_selectedDateRange = result;
});
}
}
}
Custom Flied Code
class customfields extends StatelessWidget {
customfields(
{required this.hintValue,
this.labeltxt = "",
required this.keyboardtype,
this.maxvalue = 1,
required this.text});
String hintValue;
String labeltxt;
int maxvalue;
TextInputType keyboardtype;
TextEditingController text;
bool _validate = false;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
TextFormField(
controller: text,
cursorColor: Colors.black,
//textAlign: TextAlign.left,
// autocorrect: true,
// textInputAction: TextInputAction.go,
// maxLines: maxvalue,
// autofocus: true,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
textAlignVertical: TextAlignVertical.center,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp('[a-z A-Z 0-9 #?!#%^&* ? # ^_ . : ! | "" \'-]')),
],
decoration: InputDecoration(
border: new UnderlineInputBorder(
// borderRadius: new BorderRadius.circular(5.0),
// borderSide: new BorderSide(),
),
hintText: hintValue,
labelText: labeltxt,
errorText: _validate ? 'Value Can\'t Be Empty' : null,
//prefixIcon: Icon(Icons),
//suffixIcon: Icon(Icons.currency_bitcoin)
),
keyboardType: keyboardtype,
validator: (value) {
if (value!.isEmpty) {
return "Value Can't be empty.";
}
return null;
},
),
],
),
);
}
}

Search Result does not update instantly flutter

I'm emulating this search and filter github here and the codes are almost the same but the filtered results do not update instantly while I type and also I faced the following issues:
I will have to press enter on my laptop to finally get the filtered list
When I hit the close icon(which is to clear all the words), I will have to tap the searchbar again so that all my listtile are back on the listview.
Here's my code:
class _CurrencySelectState extends State<CurrencySelect> {
late List<Currency> resCur;
String query = '';
#override
void initState() {
super.initState();
resCur = currencyList;
}
void searchCur(String query) {
final List<Currency> filteredCur = currencyList.where((cur) {
final symbolLower = cur.symbol.toLowerCase(); // Search using symbol
final nameLower = cur.country.toLowerCase(); // Search using country
final searchLower = query.toLowerCase();
return symbolLower.contains(searchLower) ||
nameLower.contains(searchLower);
}).toList();
setState(() {
this.query = query;
resCur = filteredCur;
});
}
#override
Widget build(BuildContext context) {
Widget buildCur(Currency cur) => ListTile(
leading: Padding(
padding: EdgeInset.all(5)
child: SizedBox(
child: Column(
children: <Widget>[
SvgPicture.asset(
cur.assetPath,
),
]),
),
),
title: Column(
children: [
Text(
cur.symbol,
style: TextStyle(
...
),
Text(
cur.name,
style: TextStyle(
...
),
],
),
trailing: Text(
"0.25",
style: TextStyle(
...
),
);
return TextButton(
onPressed: () async {
showModalBottomSheet(
enableDrag: false,
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return DraggableScrollableSheet(
expand: false,
builder: (context, scrollController) {
return Column(
children: <Widget>[
SearchWidget(
text: query,
onChanged: searchCur,
hintText: "Enter symbol or country"
),
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: resCur.length,
itemBuilder: (context, int index) {
final cur = resCur[index];
return buildCur(cur);
},
),
)
],
);
},
);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
...
),
SvgPicture.asset(
...
)
],
));
}
}
Searchwidget code:
import 'package:flutter/material.dart';
class SearchWidget extends StatefulWidget {
final String text;
final ValueChanged<String> onChanged;
final String hintText;
const SearchWidget({
Key? key,
required this.text,
required this.onChanged,
required this.hintText,
}) : super(key: key);
#override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final controller = TextEditingController();
#override
Widget build(BuildContext context) {
final styleActive = TextStyle(color: Colors.black);
final styleHint = TextStyle(color: Colors.black54);
final style = widget.text.isEmpty ? styleHint : styleActive;
return Container(
height: 42,
margin: const EdgeInsets.fromLTRB(16, 16, 16, 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
border: Border.all(color: Colors.black26),
),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: controller,
decoration: InputDecoration(
icon: Icon(Icons.search, color: style.color),
suffixIcon: widget.text.isNotEmpty
? GestureDetector(
child: Icon(Icons.close, color: style.color),
onTap: () {
controller.clear();
widget.onChanged('');
FocusScope.of(context).requestFocus(FocusNode());
},
)
: null,
hintText: widget.hintText,
hintStyle: style,
border: InputBorder.none,
),
style: style,
onChanged: widget.onChanged,
),
);
}
}