Search Result does not update instantly flutter - 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,
),
);
}
}

Related

My Flutter ListView is always removing the last item from the list

I'm creating a Flutter Widget and when I try to remove an item from the list I'm using, it always removes the last one, I was thinking it could be a Key problem, but nothing suits it, do anyone know how I could solve this?
The code
create_game.dart
import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';
class CreateGame extends StatefulWidget {
const CreateGame({super.key});
#override
State<CreateGame> createState() => _CreateGameState();
}
class _CreateGameState extends State<CreateGame> {
List<String> names = [''];
void changeName(int nameIndex, String change) {
setState(() {
names[nameIndex] = change;
});
}
void removeName(int nameIndex) {
print(names);
print(nameIndex);
setState(() {
names.removeAt(nameIndex);
});
}
ListView createNamesInput() {
return ListView.builder(
itemCount: names.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
key: ObjectKey(index),
title: CustomInput(
key: ObjectKey(index),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
);
},
);
// return names
// .asMap()
// .entries
// .map((el) => CustomInput(
// key: ObjectKey('${el.key}'),
// labelText: "Nome",
// onChanged: changeName,
// index: el.key,
// text: names[el.key],
// onRemoved: removeName,
// ))
// .toList();
}
void addName() {
setState(() {
names.add('');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: GestureDetector(
onTap: (() => Navigator.pop(context)),
child: const Icon(
Icons.arrow_back,
color: Colors.black,
size: 40,
),
),
backgroundColor: Colors.white,
titleTextStyle: const TextStyle(
color: Colors.black,
fontSize: 20,
),
title: const Text("CRIE SEU JOGO"),
),
body: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
// child: createNamesInput(),
child: Column(
children: [
createNamesInput(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: addName,
child: Row(
children: const [
Icon(Icons.add),
Text('Adicionar Jogador'),
],
),
),
],
),
),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => print('Iniciar!'),
child: const Text('Iniciar!'),
),
)
],
),
),
);
}
}
custom_input.dart
import 'package:flutter/material.dart';
typedef OneArgumentCallback = void Function(String changed);
class CustomInput extends StatefulWidget {
final OneArgumentCallback onChanged;
final VoidCallback onRemoved;
final String labelText;
final String text;
const CustomInput({
super.key,
required this.onChanged,
required this.labelText,
required this.text,
required this.onRemoved,
});
#override
State<CustomInput> createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
late final TextEditingController inputController;
#override
void initState() {
super.initState();
inputController = TextEditingController(text: widget.text);
}
void changeContent(String value) {
widget.onChanged(
value,
);
}
#override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: inputController,
textDirection: TextDirection.ltr,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: widget.labelText,
suffixIcon: IconButton(
onPressed: () => widget.onRemoved(),
icon: const Icon(
Icons.close,
color: Colors.red,
),
),
),
autocorrect: false,
onChanged: (value) => changeContent(value),
);
}
}
Indeed it is a key issue, you have to create a combined key that must be unique for each item, I merged the index with names[index],
CustomInput(
key: ObjectKey('$index:${names[index]}'),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
note that if you try this code alone the textfield will lose focus because the key has changed, this will be solved by removing the setState inside the onChange
void changeName(int nameIndex, String change) {
names[nameIndex] = change;
}
here you don't need setState because the UI will be updated by default when you are typing in the textfield
I hope I made it clear
I was thinking it could be a Key problem
That's correct; You need to use names[index] as the value for your Key:
ListTile(
key: ObjectKey(names[index]),
title: CustomInput(

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.

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(() {});
},
);
}
}

Checkbox doesn't change when clicked in dropdownbutton

I am using DropdownButton and I am facing the following issue. I'm using a checkbox in elements, but when I click on an element, I don't get a checkmark indicating that the checkbox has been clicked. As a result, I need to close and reopen it, and then I will see the changes that were clicked on the "checkbox". The second problem is that when I select one element, all elements are selected for me. As a final result, I need to get so that I can select an element and the checkbox is immediately marked, if 2 elements are needed, then two, and so on. Tell me how to fix these problems, I will be grateful for the help?
dropdown
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool isChecked = false;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
selectedValue = widget.items[1];
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
items: widget.items
.map((item) => DropdownMenuItem<String>(
value: item,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: Center(
child: Row(
children: [
if (item == selectedValue)
const SizedBox(
width: 0,
),
Expanded(
child: Text(
item,
style: constants.Styles.smallTextStyleWhite,
),
),
Checkbox(
checkColor: Colors.black,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
),
))
.toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
itemHeight: 47,
dropdownMaxHeight: 191,
dropdownWidth: 140,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark,
),
selectedItemBuilder: (context) {
return widget.items.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
items
final List<String> items = const [
"All EV's",
'Main EV',
'<EV2>',
];
I hope this example explains the concept. For simplcity I made simple a new file, run it and see the results:
Then main idea in two lists, _checkList contain values of the CheckBox and _selectedList handles the main dropdown widget to show the selection.
Feel free to ask any questions and I'm happy to help
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AnimationDemo(number: 5);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({Key? key, this.number = 2}) : super(key: key);
final int number;
#override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
late List<bool> _checkList;
late List<int> _selectedIndex;
bool _isOpen = false;
#override
void initState() {
_checkList = List.filled(widget.number, false);
_selectedIndex = <int>[];
super.initState();
}
List<DropDownItem> generateItems() {
var tmp = <DropDownItem>[];
for (var i = 0; i < _checkList.length; i++) {
tmp.add(DropDownItem(
isChecked: _checkList[i],
onChanged: (value) {
setState(() {
_checkList[i] = value!;
if (value && !_selectedIndex.contains(i)) {
_selectedIndex.add(i);
} else {
_selectedIndex.remove(i);
}
});
},
));
}
return tmp;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text((_selectedIndex.isEmpty)
? 'Nothing Selected'
: _selectedIndex.join(',')),
),
GestureDetector(
onTap: () {
setState(() {
_isOpen = !_isOpen;
});
},
child: const Icon(Icons.arrow_downward),
),
],
),
AnimatedOpacity(
opacity: (_isOpen) ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: generateItems(),
),
)
],
),
);
}
}
class DropDownItem extends StatelessWidget {
final bool isChecked;
final Function(bool?)? onChanged;
const DropDownItem({Key? key, this.onChanged, this.isChecked = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(child: Text('Demo item')),
Checkbox(value: isChecked, onChanged: onChanged)
],
);
}
}
Here's how to achieve the Multiselect dropdown with DropdownButton2:
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}
Also, I've added it as an example to the package doc "Example 4" so you can get back to it later.

Flutter StatefulWidget is not being rebuild

I'm facing some problems with this code..
I moved my widget "ComponentsHistoriqueDeployment" to a statefulwidget with initState() to solve some problem on focus that were rebuilding each time the widget.
So actually the data is fetched the first time, but it doesn't change when i tape something in searchbar "Sélectionnez le composant" or by changing the datePicker.
I don't understand why...
This is the parent :
class HistoriquePage extends StatefulWidget {
final String pageName;
final String namespace;
const HistoriquePage({Key? key, required this.pageName, required this.namespace}) : super(key: key);
#override
_HistoriquePageState createState() => _HistoriquePageState();
}
class _HistoriquePageState extends State<HistoriquePage> {
final _debounce = Debounce();
DateTimeRange? dateRange;
String searchedValue = "";
Post? user;
void _sendSearch(String value) {
_debounce.run(() {
setState(() {
searchedValue = value;
});
});
}
#override
Widget build(BuildContext context) => GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
appBar: AppBar(
title: Text(widget.pageName),
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
child: DateRangeField(
enabled: true,
firstDate: new DateTime(2020),
helpText: 'Sélectionnez un interval de dates',
fieldStartLabelText: 'Date de début',
fieldEndLabelText: 'Date de fin',
fieldStartHintText: 'Début',
fieldEndHintText: 'Fin',
dateFormat: DateFormat('dd/MM/yyyy'),
saveText: 'OK',
decoration: InputDecoration(
prefixIcon: Icon(Icons.date_range, color: Theme.of(context).primaryColor),
hintText: 'Sélectionnez un intervalle de dates',
hintStyle: Theme.of(context).textTheme.headline6,
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
dateRange = value!;
});
}),
),
Container(
padding: EdgeInsets.all(16),
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Theme.of(context).primaryColor),
border: OutlineInputBorder(),
labelText: 'Sélectionnez le composant',
labelStyle: Theme.of(context).textTheme.headline6),
onChanged: _sendSearch),
),
Container(height: MediaQuery.of(context).size.height - 150, child: ComponentsHistoriqueDeployment(searchedValue, dateRange: dateRange))
],
),
),
));
}
And the widget that should be rebuilt :
class ComponentsHistoriqueDeployment extends StatefulWidget {
ComponentsHistoriqueDeployment(this.searchedValue, {this.dateRange, Key? key}) : super(key: key);
final String searchedValue;
final DateTimeRange? dateRange;
#override
ComponentsHistoriqueDeploymentState createState() => ComponentsHistoriqueDeploymentState();
}
class ComponentsHistoriqueDeploymentState extends State<ComponentsHistoriqueDeployment> {
List<User>? listOfGroups;
#override
void initState() {
getGroupsList();
super.initState();
}
getGroupsList() async {
listOfGroups = await HistoriqueService.fetchHistorique(widget.searchedValue, dateRange: widget.dateRange);
setState(() {
listOfGroups = listOfGroups;
});
}
#override
Widget build(BuildContext context) {
return listOfGroups == null
? Center(child: CircularProgressIndicator())
: ListView.separated(
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemCount: listOfGroups!.length,
itemBuilder: (context, index) {
return Card(
child: Column(children: [
Padding(
padding: const EdgeInsets.only(top: 8),
child: Badge(
toAnimate: true,
animationDuration: Duration(seconds: 2),
shape: BadgeShape.square,
badgeColor: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(8),
badgeContent: Text(listOfGroups![index].name, style: TextStyle(color: Specific.getWhite, fontSize: 16)),
),
),
_displayMoreInformationOnComponent(listOfGroups, index, context)
]),
);
});
}
Widget _displayMoreInformationOnComponent(result, index, context) {
return Container(
child: ListTile(
title: Text('Tag: ' + result[index].username),
subtitle: Text('Date: ' + result[index].address.street),
leading: Icon(Icons.label),
trailing: Wrap(
spacing: 20,
children: <Widget>[
IconButton(
icon: Icon(Icons.help),
onPressed: () => Dialogs.bottomMaterialDialog(
msgStyle: TextStyle(color: Theme.of(context).textTheme.bodyText2?.color),
msg: 'Tag: ' +
result[index].name +
'\nStatus: ' +
result[index].name +
'\nDernier déploiement: ' +
result[index].name +
'\nType de route: ' +
result[index].name +
'\nDernier commit par: ' +
result[index].name +
'\n',
title: result[index].name,
color: Specific.getOrange,
context: context,
actions: [
IconsButton(
text: "OK",
iconData: Icons.check_circle,
color: Colors.green,
textStyle: TextStyle(color: Specific.getWhite),
iconColor: Specific.getWhite,
onPressed: () {
Navigator.of(context).pop();
},
),
]),
),
],
),
),
);
}
}
That's expected behaviour: initState() is only called once during widget initialization. Even though properties change their value, State object is not recreated, hence getGroupsList() is not called.
What I would recommend you to do in this case is to move the getGroupsList() up to _HistoriquePageState widget and call it on search value or date range change. Then, instead of passing searchedValue and dateRange properties to ComponentsHistoriqueDeployment, pass the listOfGroups value.
This way, you ensure that getGroupsList() is called every single time as well as the UI is updated.