Update dropdown items with setstate on focus not working - flutter

I want to update dropdown items with setState when data received from api. Dropdown items not update even though widget is rebuilt. I can only get updated dropdown items after unfocus and focus. Please point me out if something wrong in my code..
Dropdown
import 'dart:async';
import 'package:eas/app/core/theme/colors.dart';
import 'package:eas/app/core/theme/theme.dart';
import 'package:eas/app/core/widgets/form_field_input.dart';
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class AppAsyncDropdown extends StatefulWidget {
final double width;
final String hint;
final bool loading;
final List<DropdownMenuItem> items;
final Function(String search) onChanged;
const AppAsyncDropdown({
Key? key,
required this.width,
required this.hint,
required this.loading,
required this.items,
required this.onChanged,
}) : super(key: key);
#override
State<AppAsyncDropdown> createState() => _AppAsyncDropdownState();
}
class _AppAsyncDropdownState extends State<AppAsyncDropdown> {
Timer? _timer;
void debounceSearch(String search) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(
const Duration(milliseconds: 400), (() => widget.onChanged(search)));
}
#override
void dispose() {
if (_timer != null) {
_timer!.cancel();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
print('why.. ${widget.loading} - ${widget.items}');
final items = [
DropdownMenuItem(
enabled: false,
child: FormFieldInput(
controller: TextEditingController(),
width: widget.width,
hintText: 'search product',
onChanged: (value) => debounceSearch(value),
),
),
];
if (widget.loading) {
items.add(
DropdownMenuItem(
enabled: false,
child: SizedBox(
width: widget.width,
child: Center(
child: Lottie.asset(
'assets/animation/search_loading.json',
width: 150,
)),
),
),
);
}
if (widget.items.isNotEmpty) {
items.addAll(widget.items);
}
return SizedBox(
width: widget.width,
child: DropdownButtonFormField<dynamic>(
isExpanded: true,
decoration: InputDecoration(
hintText: widget.hint,
hintStyle: AppTheme.of(context).subtitle4,
border: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black)),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: AppColors.primary),
),
),
items: items,
onChanged: (dynamic e) {
print('hii $e');
},
),
);
}
}
I added debounce to prevent requesting on every keystroke.And by debugging with print and some ui changes, it is verified that states are updated..
Script That Update State
import 'dart:async';
import 'package:eas/app/core/layout/desktop/content_desktop.dart';
import 'package:eas/app/core/layout/desktop/header_desktop.dart';
import 'package:eas/app/core/layout/desktop/layout_desktop.dart';
import 'package:eas/app/core/theme/colors.dart';
import 'package:eas/app/core/theme/miscs.dart';
import 'package:eas/app/core/theme/sizedBoxes.dart';
import 'package:eas/app/core/theme/sizes.dart';
import 'package:eas/app/core/theme/theme.dart';
import 'package:eas/app/core/widgets/button.dart';
import 'package:eas/app/core/widgets/dropdown.dart';
import 'package:eas/app/core/widgets/dynamic_list.dart';
import 'package:eas/app/routes/app_pages.dart';
import 'package:flutter/material.dart';
class TransferStockForm extends StatefulWidget {
const TransferStockForm({Key? key}) : super(key: key);
#override
State<TransferStockForm> createState() => _TransferStockFormState();
}
class _TransferStockFormState extends State<TransferStockForm> {
Timer? _timer;
List<DropdownMenuItem> _branchWarehouses = [];
bool _isLoading = false;
void updateBranchWarehouse(String search) {
print('searched - $search');
setState(() {
_isLoading = true;
_branchWarehouses = [];
});
_timer = Timer(
const Duration(seconds: 2),
() {
setState(() {
_isLoading = false;
_branchWarehouses = const [
DropdownMenuItem(
value: '1',
child: Text('one'),
),
DropdownMenuItem(
value: '2',
child: Text('two'),
),
DropdownMenuItem(
value: '3',
child: Text('three'),
),
];
});
},
);
}
#override
void dispose() {
if (_timer != null) {
_timer!.cancel();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: AppDesktopLayout(
header: const AppDesktopHeader(
title: "Transfer Stock",
pageName: AppRoutes.transferStockForm,
isFavourable: true,
),
currentNavi: AppRoutes.home,
content: AppDesktopContent(
child: Container(
padding: const EdgeInsets.all(AppSizes.md),
child: Container(
padding: const EdgeInsets.all(AppSizes.md),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(
Radius.circular(AppSizes.sm),
),
boxShadow: [
AppThemeMiscs.shadow2,
],
),
child: LayoutBuilder(builder: (context, constraint) {
return Row(
children: [
SizedBox(
width: constraint.maxWidth * 0.35,
height: constraint.maxHeight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'To',
style: AppTheme.of(context).headline3,
),
AppSizeBoxes.sm,
AppAsyncDropdown(
width: constraint.maxWidth * 0.35,
hint: 'select branch or warehouse to transfer to',
loading: _isLoading,
items: _branchWarehouses,
onChanged: (String search) =>
updateBranchWarehouse(search),
),
AppSizeBoxes.md,
Button(onPressed: () {}, text: 'Transfer'),
],
),
),
const Expanded(
child: DynamicList(
type: 'stock',
)),
],
);
}),
),
),
)),
);
}
}
faked api response with timer

Related

How to properly record local storage(sharedpreferences) for a list(todolist)

I am new to flutter. I have a task to create a local repository for a task list. I tried many options to write the code but none of them worked. I read and watched a lot of videos and articles. Please write your options how I can record it
Here is my code:
import 'dart:convert';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Home extends StatefulWidget {
const Home ({key}): super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final myController = TextEditingController();
bool submit = false;
Color mainColor = Color(0xFFEEEFF5);
Color secColor = Color(0xFF3A3A3A);
Color tdBlue = Color(0xFF5F52EE);
String temp = "";
List todoList = [];
#override
void initState() {
super.initState();
myController.addListener(() {
setState(() {
submit = myController.text.isNotEmpty;
});
});
todoList.addAll(['Biy milk','Dishhh Wash','Придбати картоплю', 'Buy cucumbers', 'Kylunuch'],);
}
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void clearText() {
myController.clear();
}
#override
Widget build(BuildContext context){
return Scaffold(
backgroundColor: mainColor,
appBar: AppBar(
elevation: 0.0,
backgroundColor: secColor,
title: Text ('ToDo - List', style: TextStyle(fontSize: 26.5, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic),),
),
body: Column(
children: [
Container(
margin: EdgeInsets.only(
top: 15.0,
left: 13.0,
right: 8.0,
),
child: Row(
children: [
Expanded(
child: TextField(
onChanged: (String value) {
temp = value;
},
controller: myController,
decoration:
InputDecoration(
prefixIcon: Icon(Icons.notes, color: Colors.orangeAccent,),
labelText: 'Замітка',
hintText: 'Введіть замітку',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10.0),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0),),
),
),
),
),
SizedBox(
width: 10.0,
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: tdBlue),
onPressed:
submit ? () => submitData() : null,
child:
Text('+', style: TextStyle(fontSize: 35),),
),
], // Закривається 2-ий чілдрен
), //Row 1-ий
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
top: 15.0,
),
child: ListView.builder(
itemCount: todoList.length,
itemBuilder: (BuildContext context, int index){
return Dismissible(
key: Key(todoList[index]),
child: Card(
margin: EdgeInsets.only(
top: 16.0,
left: 25.0,
right: 25.0,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0)),
elevation: 0.0,
child:
ListTile(
leading: Icon(Icons.delete_sweep, color: Colors.orangeAccent,),
title: Text(todoList[index],
),),
),
onDismissed: (direction){
if (direction == DismissDirection.endToStart) {
setState(() {
todoList.removeAt(index);
});// Закривається Сетстейт (")") і }
}
}, // Закривається ОнДісмісед
);
},
),
)
),
], // Закривається 1-ий чілдрен
),
);
}
submitData() {
setState(() {
todoList.add(temp);
clearText();
});
} // submit data
}
I tried many options to write the code but none of them worked.
Try this
List<String> todoList = [];
#override
void initState() {
super.initState();
myController.addListener(() {
setState(() {
submit = myController.text.isNotEmpty;
});
});
omLoadData();
}
submitData() async {
setState(() {
todoList.add(temp);
clearText();
});
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList('todo_list', todoList);
}
omLoadData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final data = prefs.getStringList("todo_list");
todoList.addAll(data!);
}

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

TextEditingController not passing text into named parameters

I am really struggling to understand why my code isn't working. I'm trying to pass the text from two controllers into another widget with named parameters which writes to Firebase.
My "Test" button properly prints both _titleController.text and _descriptionController.text
TextButton(
onPressed: (){
print(_titleController.text); //works fine
print(_descriptionController.text); //works fine
},
child: Text('test')
),
However when I pass these into my next widget it's blank! If I hardcore strings into these parameters it works properly:
PushNewE3 (
changeTitle: _titleController.text, //does not work (empty)
changeDescription: _descriptionController.text, //does not work (empty)
)
Full code:
class CreateE3 extends StatefulWidget {
const CreateE3({Key? key}) : super(key: key);
#override
_CreateE3State createState() => _CreateE3State();
}
class _CreateE3State extends State<CreateE3> {
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
#override
void initState(){
super.initState();
_titleController.addListener(_printLatestValue);
}
#override
void dispose(){
_titleController.dispose();
super.dispose();
}
void _printLatestValue(){
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('So Frustrating'),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 800,
child: Column(
children: [
Text('Originator: **Add Current User**') ,
TextField(
maxLength: 40,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Change Title'
),
controller: _titleController,
onEditingComplete: (){
//_title = _titleController.text;
},
),
Padding(
padding: const EdgeInsets.fromLTRB(0,10,0,0),
child: TextFormField(
maxLines: 5,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Detailed Description'
),
controller: _descriptionController,
),
),
TextButton(
onPressed: (){
print(_titleController.text); //successfully prints
print(_descriptionController.text); //successfully prints
},
child: Text('test')
),
PushNewE3 (
changeTitle: _titleController.text, //DOES NOT WORK (empty)
changeDescription: _descriptionController.text, //DOES NOT WORK (empty)
)
],
),
),
],
),
);
}
}
class PushNewE3 extends StatelessWidget {
final String changeTitle;
final String changeDescription;
PushNewE3({
required this.changeTitle,
required this.changeDescription
});
#override
Widget build(BuildContext context) {
// Create a CollectionReference called users that references the firestore collection
CollectionReference notificationsE3 = FirebaseFirestore.instance.collection('notificationsE3');
Future<void> pushNewE3() {
// Call the notifications CollectionReference to add a new E3 notification
return notificationsE3
.add({
//'originator': FirebaseAuth.instance.currentUser,
'changeTitle': changeTitle,
'changeDescription': changeDescription,
})
.then((value) => print("E3 Created"))
.catchError((error) => print("Failed to create E3: $error"));
}
return TextButton(
onPressed: (){
print('start:');
print(changeTitle);
print(changeDescription);
print('-end');
},
child: Text(
"Create E3",
),
);
}
}
EDIT:
I still don't understand why the above code doesn't work. I refactored my code into a single widget and now it's working. If anyone can explain why I would still appreciate understanding as there is clearly a gap in my knowledge.
If anyone in the future runs into the same problem here is the refactored code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'main.dart';
var global = 'blank';
class CreateE3 extends StatefulWidget {
const CreateE3({Key? key}) : super(key: key);
#override
_CreateE3State createState() => _CreateE3State();
}
class _CreateE3State extends State<CreateE3> {
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
// Create a CollectionReference called users that references the firestore collection
CollectionReference notificationsE3 = FirebaseFirestore.instance.collection('notificationsE3');
Future<void> pushNewE3() {
// Call the notifications CollectionReference to add a new E3 notification
return notificationsE3
.add({
//'originator': FirebaseAuth.instance.currentUser,
'changeTitle': _titleController.text,
'changeDescription': _descriptionController.text,
})
.then((value) => print("E3 Created"))
.catchError((error) => print("Failed to create E3: $error"));
}
return Scaffold(
appBar: AppBar(
title: Text(_titleController.text),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 800,
child: Column(
children: [
Text('Originator: **Add Current User**') ,
TextField(
maxLength: 40,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Change Title'
),
controller: _titleController,
onChanged: (text){
setState(() {
});
},
),
Padding(
padding: const EdgeInsets.fromLTRB(0,10,0,0),
child: TextFormField(
maxLines: 5,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Detailed Description'
),
controller: _descriptionController,
),
),
TextButton(
onPressed: (){
pushNewE3();
},
child: Text('SAVE')
),
],
),
),
],
),
);
}
}
in the onPressed To pass a value and show it, you have to use setState(() { _myState = newValue; });
Something like this
TextButton(
onPressed: (){
print(_titleController.text);
print(_descriptionController.text);
setState(() { _myNewText = _titleController.text; });
},
child: Text('test')
),
I'm not sure what are you trying to do exactly but here's what I did:
1 - add a local variable _title
2 - add this code to the onPressed function:
setState(() {
_title= _titleController.text;
});
This is the whole code :
class CreateE3 extends StatefulWidget {
const CreateE3({Key? key}) : super(key: key);
#override
_CreateE3State createState() => _CreateE3State();
}
class _CreateE3State extends State<CreateE3> {
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
String _title = 'So Frustrating';
#override
void initState(){
super.initState();
}
#override
void dispose(){
_titleController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 400,
child: Column(
children: [
Text('Originator: **Add Current User**') ,
TextField(
maxLength: 40,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Change Title'
),
controller: _titleController,
onEditingComplete: (){
//_title = _titleController.text;
},
),
Padding(
padding: const EdgeInsets.fromLTRB(0,10,0,0),
child: TextFormField(
maxLines: 5,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Detailed Description'
),
controller: _descriptionController,
),
),
TextButton(
onPressed: (){
print(_titleController.text);
print(_descriptionController.text);
setState(() {
_title = _titleController.text;
});
},
child: Text('test')
),
],
),
),
],
),
);
}
}
.........................
so this is when you first start the app :
after changing the TextField and pressing the 'test button the title in the appbar change :

FLUTTER MOBX: A build function returned null. The relevant error-causing widget was Observer

I'm trying to do a verification in two fields, email and password, using MOBX, and I'm computing the result of two functions, in a compuntig called formIsValid, but mobX has returned this error to me: A build function returned null.
The relevant error-causing widget was
Observer
I tried to do it in different ways and I can't, and besides that my email observables emailErrorLabel and passwordErrorLabel are not affecting the TextTormField errorText.
Here is my code ViewModel:
import 'package:covid_app/app/service/firebase/firebase_auth.dart';
import 'package:covid_app/app/service/firebase/firebase_auth_impl.dart';
import 'package:covid_app/app/ui/home/home_page.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
part 'login_viewmodel.g.dart';
class LoginViewModel = LoginViewModelBase with _$LoginViewModel;
abstract class LoginViewModelBase with Store {
#observable
String email = "";
#observable
String password = "";
#observable
bool error = false;
#observable
bool emailErrorLabel = false;
#observable
bool passwordErrorLabel = false;
final _auth = Auth();
#action
changeEmail(String newEmail) => email = newEmail;
#action
changePassword(String newPassword) => password = newPassword;
#action
setHasErrorOnEmail(bool value) => emailErrorLabel = value;
#action
setHasErrorOnPassword(bool value) => passwordErrorLabel = value;
bool emailIsValid() {
if (email.isNotEmpty && email.contains("#")) {
return true;
} else {
setHasErrorOnEmail(true);
return false;
}
}
bool passwordIsValid() {
if (password.isNotEmpty || password.length >= 8) {
return true;
} else {
setHasErrorOnPassword(true);
return false;
}
}
#computed
bool get formIsValid {
return emailIsValid() && passwordIsValid();
}
#action
Future<void> firebaseLogin(dynamic context) async {
try {
if (email.isNotEmpty && password.isNotEmpty) {
var userId;
await _auth.signIn(email, password).then((value) => userId = value);
userId.length > 0 ? homeNavigator(context) : error = true;
} else {
error = true;
}
} catch (Exception) {
error = true;
print("Login Error: $Exception");
}
}
void homeNavigator(context) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
}
My LoginPage:
import 'package:covid_app/app/ui/login/login_viewmodel.dart';
import 'package:covid_app/app/widgets/KeyboardHideable.dart';
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:covid_app/core/constants/string.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../../widgets/button_component.dart';
import 'widgets/text_form_field_component.dart';
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
var vm = LoginViewModel();
var value = zero;
var valueTextFields = sixtyEight;
TextEditingController controllerEmail = TextEditingController();
TextEditingController controllerPassword = TextEditingController();
void animatedTest() async {
Future.delayed(Duration(seconds: 0), () {
setState(() {
value = sixtyEight;
valueTextFields = zero;
});
});
}
#override
void initState() {
super.initState();
animatedTest();
}
#override
Widget build(BuildContext context) {
return KeyboardHideable(
child: Scaffold(
backgroundColor: darkPrimaryColor,
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(sixteen),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Card(
elevation: twelve,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(twentyFour)),
),
child: Padding(
padding: const EdgeInsets.all(thirtyTwo),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(),
Expanded(
flex: 8,
child: AnimatedContainer(
margin: EdgeInsets.only(bottom: value),
duration: Duration(seconds: 1),
child: Image.asset(
"assets/images/logo_covid_app.png")),
),
Expanded(
flex: 7,
child: AnimatedContainer(
margin:
EdgeInsets.only(top: valueTextFields),
duration: Duration(seconds: 1),
child: Column(
children: <Widget>[
Expanded(
flex: 2,
child: Observer(
builder: (_) =>
TextFormFieldComponent(
emailHintText,
false,
controllerEmail,
vm.changeEmail,
vm.emailErrorLabel,
emailErrorLabel),
),
),
Expanded(
flex: 2,
child: Observer(builder: (_) {
return TextFormFieldComponent(
passwordHintText,
true,
controllerPassword,
vm.changePassword,
vm.emailErrorLabel,
passwordErrorLabel);
}),
),
],
),
),
),
SizedBox(
height: twentyEight,
),
Expanded(
flex: 2,
child: Observer(
builder: (_) => ButtonComponent(
title: loginButtonLabel,
fillColor: rosePrimaryColor,
textColor: Colors.white,
loginFun: vm.formIsValid
? () => vm.firebaseLogin(context)
: null,
),
),
),
SizedBox(
height: twenty,
),
Expanded(
flex: 2,
child: ButtonComponent(
title: registerButtonLabel,
fillColor: darkPrimaryColor,
textColor: Colors.white,
loginFun: () {}),
),
Spacer()
],
),
),
),
),
],
),
),
),
),
),
),
),
);
}
}
My TextFormField Component:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class TextFormFieldComponent extends StatefulWidget {
String hintText;
bool hideText;
TextEditingController genericControler;
bool genericValidation;
String errorMessage;
Function onChangedGeneric;
TextFormFieldComponent(this.hintText, this.hideText, this.genericControler,
this.onChangedGeneric, this.genericValidation, this.errorMessage);
#override
_TextFormFieldComponentState createState() => _TextFormFieldComponentState();
}
class _TextFormFieldComponentState extends State<TextFormFieldComponent> {
#override
Widget build(BuildContext context) {
return Theme(
data:
ThemeData(cursorColor: rosePrimaryColor, hintColor: darkPrimaryColor),
child: TextFormField(
onChanged: widget.onChangedGeneric,
controller: widget.genericControler,
obscureText: widget.hideText,
decoration: InputDecoration(
hintText: widget.hintText,
errorText: widget.genericValidation == true ? widget.errorMessage : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(width: two, color: darkPrimaryColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(twentyFour)),
borderSide: BorderSide(
width: two,
color: rosePrimaryColor,
),
),
),
),
);
}
}
My Button Component:
import 'package:covid_app/core/constants/colors.dart';
import 'package:covid_app/core/constants/dimens.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class ButtonComponent extends StatefulWidget {
var title;
var fillColor;
var textColor;
Function loginFun;
ButtonComponent({Key key, this.title, this.fillColor, this.textColor, this.loginFun});
#override
_ButtonComponentState createState() => _ButtonComponentState();
}
class _ButtonComponentState extends State<ButtonComponent> {
#override
Widget build(BuildContext context) {
return Container(
width: hundredSeventyTwo,
height: fortyFour,
child: RaisedButton(
disabledColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
twentyFour,
),
),
onPressed: widget.loginFun,
color: widget.fillColor,
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
),
),
),
);
}
}
Print of the Error: https://i.stack.imgur.com/UmQd5.png / https://i.stack.imgur.com/K13rN.png
Well, the error says it all, nothing to add really.
Inside formIsValid computed you invoke 2 functions which in turn might modify emailErrorLabel or passwordErrorLabel and, since they both observable and are used in the same render, this is not allowed.
computed should be pure function without side effects, it should just derive some value from other computed, observable or constant values.

Is there a number input field in flutter with increment/decrement buttons attached to the field?

I am trying to create a number input field with and up and down arrow button to increment and decrement its value. I am wondering if there is any inbuilt widget which already provides this functionality. I have to create couple of such fields in my UI and creating so many stateful widgets makes me wonder if there is any simpler approach.
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Increment Decrement Demo';
return MaterialApp(
title: title,
home: NumberInputWithIncrementDecrement(),
);
}
}
class NumberInputWithIncrementDecrement extends StatefulWidget {
#override
_NumberInputWithIncrementDecrementState createState() =>
_NumberInputWithIncrementDecrementState();
}
class _NumberInputWithIncrementDecrementState
extends State<NumberInputWithIncrementDecrement> {
TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.text = "0"; // Setting the initial value for the field.
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Number Field increment decrement'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: TextFormField(
controller: _controller,
keyboardType: TextInputType.numberWithOptions(
decimal: false, signed: false),
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
minWidth: 5.0,
child: Icon(Icons.arrow_drop_up),
onPressed: () {
int currentValue = int.parse(_controller.text);
setState(() {
currentValue++;
_controller.text =
(currentValue).toString(); // incrementing value
});
},
),
MaterialButton(
minWidth: 5.0,
child: Icon(Icons.arrow_drop_down),
onPressed: () {
int currentValue = int.parse(_controller.text);
setState(() {
print("Setting state");
currentValue--;
_controller.text =
(currentValue).toString(); // decrementing value
});
},
),
],
),
Spacer(
flex: 2,
)
],
),
),
);
}
}
current output looks some thing like this.
I am looking for something like the following in a simpler manner like in HTML number input field.
I have laid out my Number input widget as shown below. I think I will go ahead with this approach until someone has any different idea for the same.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Increment Decrement Demo';
return MaterialApp(
title: title,
home: NumberInputWithIncrementDecrement(),
);
}
}
class NumberInputWithIncrementDecrement extends StatefulWidget {
#override
_NumberInputWithIncrementDecrementState createState() =>
_NumberInputWithIncrementDecrementState();
}
class _NumberInputWithIncrementDecrementState
extends State<NumberInputWithIncrementDecrement> {
TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.text = "0"; // Setting the initial value for the field.
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Number Field increment decrement'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Container(
width: 60.0,
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
color: Colors.blueGrey,
width: 2.0,
),
),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: TextFormField(
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
controller: _controller,
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
),
),
Container(
height: 38.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
),
),
),
child: InkWell(
child: Icon(
Icons.arrow_drop_up,
size: 18.0,
),
onTap: () {
int currentValue = int.parse(_controller.text);
setState(() {
currentValue++;
_controller.text = (currentValue)
.toString(); // incrementing value
});
},
),
),
InkWell(
child: Icon(
Icons.arrow_drop_down,
size: 18.0,
),
onTap: () {
int currentValue = int.parse(_controller.text);
setState(() {
print("Setting state");
currentValue--;
_controller.text =
(currentValue > 0 ? currentValue : 0)
.toString(); // decrementing value
});
},
),
],
),
),
],
),
),
),
),
);
}
}
Update:
As I see many of us like this approach I created a package for the same. Maybe its helpful for some of us.
number_inc_dec
I was looking for a simple -/+ step counter, so I made one... don't pretend too much, I'm using flutter since a couple of days :-)
It has a maximum and minimum value, by default the minimum is set to zero and maximum to 10, but if you need negative values, just set it to -N.
Preview
Widget source
import 'package:flutter/material.dart';
class NumericStepButton extends StatefulWidget {
final int minValue;
final int maxValue;
final ValueChanged<int> onChanged;
NumericStepButton(
{Key key, this.minValue = 0, this.maxValue = 10, this.onChanged})
: super(key: key);
#override
State<NumericStepButton> createState() {
return _NumericStepButtonState();
}
}
class _NumericStepButtonState extends State<NumericStepButton> {
int counter= 0;
#override
Widget build(BuildContext context) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(
Icons.remove,
color: Theme.of(context).accentColor,
),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 18.0),
iconSize: 32.0,
color: Theme.of(context).primaryColor,
onPressed: () {
setState(() {
if (counter > widget.minValue) {
counter--;
}
widget.onChanged(counter);
});
},
),
Text(
'$counter',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black87,
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
IconButton(
icon: Icon(
Icons.add,
color: Theme.of(context).accentColor,
),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 18.0),
iconSize: 32.0,
color: Theme.of(context).primaryColor,
onPressed: () {
setState(() {
if (counter < widget.maxValue) {
counter++;
}
widget.onChanged(counter);
});
},
),
],
),
);
}
}
Read the counter value
...
int yourLocalVariable = 0;
...
return Container(
child: NumericStepButton(
maxValue: 20,
onChanged: (value) {
yourLocalVariable = value;
},
),
)
],
...
Happy coding!
This is the most complete solution you can find
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class NumberTextField extends StatefulWidget {
final TextEditingController? controller;
final FocusNode? focusNode;
final int min;
final int max;
final int step;
final double arrowsWidth;
final double arrowsHeight;
final EdgeInsets contentPadding;
final double borderWidth;
final ValueChanged<int?>? onChanged;
const NumberTextField({
Key? key,
this.controller,
this.focusNode,
this.min = 0,
this.max = 999,
this.step = 1,
this.arrowsWidth = 24,
this.arrowsHeight = kMinInteractiveDimension,
this.contentPadding = const EdgeInsets.symmetric(horizontal: 8),
this.borderWidth = 2,
this.onChanged,
}) : super(key: key);
#override
State<StatefulWidget> createState() => _NumberTextFieldState();
}
class _NumberTextFieldState extends State<NumberTextField> {
late TextEditingController _controller;
late FocusNode _focusNode;
bool _canGoUp = false;
bool _canGoDown = false;
#override
void initState() {
super.initState();
_controller = widget.controller ?? TextEditingController();
_focusNode = widget.focusNode ?? FocusNode();
_updateArrows(int.tryParse(_controller.text));
}
#override
void didUpdateWidget(covariant NumberTextField oldWidget) {
super.didUpdateWidget(oldWidget);
_controller = widget.controller ?? _controller;
_focusNode = widget.focusNode ?? _focusNode;
_updateArrows(int.tryParse(_controller.text));
}
#override
Widget build(BuildContext context) => TextField(
controller: _controller,
focusNode: _focusNode,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.number,
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
decoration: InputDecoration(
counterText: '',
isDense: true,
filled: true,
fillColor: Theme.of(context).colorScheme.surface,
contentPadding: widget.contentPadding.copyWith(right: 0),
suffixIconConstraints: BoxConstraints(
maxHeight: widget.arrowsHeight, maxWidth: widget.arrowsWidth + widget.contentPadding.right),
suffixIcon: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(widget.borderWidth), bottomRight: Radius.circular(widget.borderWidth))),
clipBehavior: Clip.antiAlias,
alignment: Alignment.centerRight,
margin: EdgeInsets.only(
top: widget.borderWidth,
right: widget.borderWidth,
bottom: widget.borderWidth,
left: widget.contentPadding.right),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Expanded(
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Opacity(opacity: _canGoUp ? 1 : .5, child: const Icon(Icons.arrow_drop_up)),
onTap: _canGoUp ? () => _update(true) : null))),
Expanded(
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Opacity(opacity: _canGoDown ? 1 : .5, child: const Icon(Icons.arrow_drop_down)),
onTap: _canGoDown ? () => _update(false) : null))),
]))),
maxLines: 1,
onChanged: (value) {
final intValue = int.tryParse(value);
widget.onChanged?.call(intValue);
_updateArrows(intValue);
},
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
void _update(bool up) {
var intValue = int.tryParse(_controller.text);
intValue == null ? intValue = 0 : intValue += up ? widget.step : -widget.step;
_controller.text = intValue.toString();
_updateArrows(intValue);
_focusNode.requestFocus();
}
void _updateArrows(int? value) {
final canGoUp = value == null || value < widget.max;
final canGoDown = value == null || value > widget.min;
if (_canGoUp != canGoUp || _canGoDown != canGoDown)
setState(() {
_canGoUp = canGoUp;
_canGoDown = canGoDown;
});
}
}
class _NumberTextInputFormatter extends TextInputFormatter {
final int min;
final int max;
_NumberTextInputFormatter(this.min, this.max);
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
if (const ['-', ''].contains(newValue.text)) return newValue;
final intValue = int.tryParse(newValue.text);
if (intValue == null) return oldValue;
if (intValue < min) return newValue.copyWith(text: min.toString());
if (intValue > max) return newValue.copyWith(text: max.toString());
return newValue.copyWith(text: intValue.toString());
}
}
Simple, BLoC-friendly approach:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _controller = TextEditingController();
final _streamController = StreamController<int>();
Stream<int> get _stream => _streamController.stream;
Sink<int> get _sink => _streamController.sink;
int initValue = 1;
#override
void initState() {
_sink.add(initValue);
_stream.listen((event) => _controller.text = event.toString());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () {
_sink.add(--initValue);
},
child: Icon(Icons.remove)),
Container(
width: 50,
child: TextField(
controller: _controller,
),
),
TextButton(
onPressed: () {
_sink.add(++initValue);
},
child: Icon(Icons.add)),
],
)
],
),
),
);
}
#override
void dispose() {
_streamController.close();
_controller.dispose();
super.dispose();
}
}