How to scroll dialog content to focused TextField in Flutter? - flutter

I have a dialog with long content and many TextFields - at the top, middle and bottom. This is my test code
import 'package:flutter/material.dart';
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
#override
Widget build(BuildContext context) {
var textController = TextEditingController(text: "");
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return AlertDialog(
content: Container(
width: width,
height: height,
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.green,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(height: 500,),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
],
),
),
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
}
);
},
child: Text("Tap me"))
],
),
);
}
}
And this is the result:
As you see TextField that is at the bottom is not visible on focus - scrollview doesnt scroll to its position.
Could anyone say how to fix this issue. Please, note, that solution needs to support multiple TextFields (as I've said I have many of them).
EDIT 1
I tried to use scrollable positioned list. This is my code
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
width: 300,
height: 500,
child: ScrollablePositionedList.builder(
padding: EdgeInsets.only(top: 10),
itemCount: 2,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
return Focus(
child: Padding(
padding: EdgeInsets.only(bottom: 500),
child: TextField(
key: ValueKey("_k" + index.toString()),
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
//controller: textController,
),
),
onFocusChange: (hasFocus) {
if (hasFocus) {
// itemScrollController.jumpTo(index: index);
itemScrollController.scrollTo(
index: index,
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
} ,
);
},
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
}
);
},
child: Text("Tap me"))
],
),
);
}
}
and this is the result:
As you see the problem is that when keyboard is shown it doesn't scroll to focused item.

Your code works absolutely fine on both devices. I have added a gesture detector to the Container since the textfields were multi-lined so there was no option to lower the keyboard in iOS. Here is the code that I have used
import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as img;
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: TempScreen());
}
}
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
#override
Widget build(BuildContext context) {
var textController = TextEditingController(text: "");
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return AlertDialog(
content: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: width,
height: height,
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.purple,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.green,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.blue,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.yellow,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
],
),
),
),
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
});
},
child: Text("Tap me"))
],
),
);
}
}
EDIT
You can create a key and assign it to the textfield and you can use this key and scroll to that widget's position like this.
final dataKey = GlobalKey();
TextField(
key: dataKey,
textAlign: TextAlign.left,
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
onTap: () {
Scrollable.ensureVisible(dataKey.currentContext!);//here you can scroll to the respective widget referring the key.
},
),
Please note that if you have a lot of textfields, this may result in some performance issue.. But scrolling to the right widget will work.

I managed to achieve that (not by myself, but I can't recall where I found help) in a similar case.
My widget structure is: AlertDialog -> Container (for width setting) -> StatefulBuilder. The last one, after a bit of preparation, returns this:
return Form(
key: _formKey,
child: ScrollablePositionedList.builder(
padding: EdgeInsets.only(top: 10),
itemCount: childrenList.length,
itemBuilder: (context, index) => childrenList[index],
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
));
where
childrenList contains the list of widgets, most of them being TextFormField
itemBuilder just takes the items in order
itemScrollController is just final ItemScrollController itemScrollController = ItemScrollController();
itemPositionsListener is just final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
I don't think that there's anything else involved in the "auto-scrolling" thing, but I wrote this a while ago, so let me know if something's missing.

Related

How to set top padding for bottom sheet with text field in Flutter?

I need 80 pixel top padding (so AppBar to be shown) for my bottom sheet when keyboard is visible and when keyboard is not visible.
This is my code:
import 'package:flutter/material.dart';
class Temp2Screen extends StatelessWidget {
const Temp2Screen({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController scrollController;
List<String> messages = [
"msg1", "msg2", "msg3", "msg4", "msg5", "msg6", "msg7", "msg8", "msg9", "msg10",
];
#override
void initState() {
super.initState();
scrollController = new ScrollController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(25.0))),
backgroundColor: Colors.yellow,
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[
Text(m)
]
],
),
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
)
],
),
),
)
);
},
child: const Text('Show Modal Bottom Sheet'),
),
));
}
}
When keyboard is not visible everything is ok (system top panel is visible and AppBar is visible):
However, when keyboard is visible I have a problem as bottom sheet covers both top panel and and AppBar:
Could anyone say how to fix this problem so top panel and AppBar to be visible in both cases (when keyboard is on and when it is off)?
Instead of wrap your whole bottom sheet with padding, try wrap your textField with padding, like this:
builder: (context) => SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[Text(m)]
],
),
),
Padding(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context).viewInsets.bottom),
child: TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
),
)
],
),
));

I can't change my widget's visibility with using mobx

I'm trying to change my second TextField's visibility in my other Auth class but I'm using mobx. I tried this version. But I can't solve my problem anyway.
If I declare my otpvisibility variable in SignInView class, I need to use setstate when i change the value of this variable in the Auth class. But i can't use setstate because I am using mobx. On the other hang if I declare otpvisibility variable in my mobx class, changes won't effect.
class SignInView extends StatelessWidget {
final BuildContext context;
SignInView({required this.context, Key? key}) : super(key: key);
TextEditingController phonecontroller = TextEditingController();
TextEditingController otpcontroller = TextEditingController();
Auth auth = Auth();
SignInViewModel svm = SignInViewModel();
#override
Widget build(BuildContext context) {
return Scaffold(
body: buildbody,
);
}
Widget get buildbody {
double screenWidth = MediaQuery.of(context).size.width;
return Observer(builder: (_) {
return SizedBox(
width: screenWidth,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Welcome to Whatsapp Clone, Let's begin!",
style: TextStyle(color: Colors.green, fontSize: 20),
),
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.all(8),
height: 80,
child: TextField(
controller: phonecontroller,
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide()),
),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done,
onSubmitted: (String value) {
if (phonecontroller.text != '') {
if (svm.otpVisibility) {
svm.otpvisiblty(context);
auth.verifyotp(context, otpcontroller.text);
} else {
print('otpvisible false');
auth.loginWithPhone(
context: context, phone: phonecontroller.text);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Please enter your phone number')));
}
},
)),
Visibility(
visible: svm.otpVisibility,
child: Container(
padding: const EdgeInsets.all(8),
height: 80,
child: TextField(
controller: otpcontroller,
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(),
),
),
keyboardType: TextInputType.number,
maxLength: 6,
),
),
),
TextButton(
onPressed: () {
print(phonecontroller.text);
if (phonecontroller.text != '') {
if (svm.otpVisibility) {
svm.otpvisiblty(context);
auth.verifyotp(context, otpcontroller.text);
} else {
print('otpvisible false');
auth.loginWithPhone(
context: context, phone: phonecontroller.text);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Please enter your phone number')));
}
},
child: const Text(
'Verify',
style: TextStyle(color: Colors.green),
),
)
],
),
),
);
});
}
}

Validate Elevated Button in Flutter

I'm making an app using Flutter which calculates motor vehicle tax.
It calculates it perfectly fine when I enter the cost of vehicle.
But I want to add a validation to it. When I don't enter any cost of vehicle and keeps it empty and then click the calculate button, I want it show - please enter the cost.
How do I add this validation as this is not a form.
Here is the code of that part:
TextField(
controller: costController,
decoration: const InputDecoration(labelText: "Cost of Vehicle"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
),
const SizedBox(
height: 20,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).primaryColor,
),
onPressed: () {
setState(() {
toPrint = calc(
dropDownValue!,
int.parse(costController.text),
).toString();
});
},
child: const Text("Calculate")),
const SizedBox(
height: 20,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.lightGreenAccent[100],
border: const Border(
bottom: BorderSide(color: Colors.grey),
)),
child: Text("Tax : $toPrint "),
),
Wrap the column with a Form widget add avalidator to the textfield
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
const appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const MyCustomForm(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({super.key});
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: costController,
decoration: const InputDecoration(labelText: "Cost of Vehicle"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).primaryColor,
),
onPressed: () {
if (_formKey.currentState!.validate()) {
setState(() {
toPrint = calc(
dropDownValue!, int.parse(costController.text),
).toString();
});
}
},
child: const Text("Calculate")),
const SizedBox(
height: 20,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.lightGreenAccent[100],
border: const Border(
bottom: BorderSide(color: Colors.grey),
)),
child: Text("Tax : $toPrint "),
),
],
),
);
}
}
Use Form Widget and Convert TextField to TextFormField like that.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FormWidget extends StatefulWidget {
const FormWidget({Key? key}) : super(key: key);
#override
State<FormWidget> createState() => _FormWidgetState();
}
class _FormWidgetState extends State<FormWidget> {
final TextEditingController costController = TextEditingController();
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _formKey,
body: Column(
children: [
Form(
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return "Please enter the cost.";
}
return null;
},
controller: costController,
decoration: const InputDecoration(labelText: "Cost of Vehicle"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
),
),
const SizedBox(
height: 20,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).primaryColor,
),
onPressed: () {
if(_formKey.currentState.validate()){
//do your setState stuff
setState(() {
});
}
},
child: const Text("Calculate")),
const SizedBox(
height: 20,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.lightGreenAccent[100],
border: const Border(
bottom: BorderSide(color: Colors.grey),
)),
child: Text("Tax : "),
),
],
),
);
}
}

Using TextEditingControllers in AnimatedList to dynamically update data

I am implementing AnimatedList on my app with several TextEditingControllers. I would like to dynamically update, insert and remove data. I've read this question and an article on how to update data in an AnimatedList and this is how my code looks:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
List<Board> boards = [Board.empty()];
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
void Function()? removeItemCallback(int index) {
if (boards.length <= 1) {
return null;
}
return (() {
FocusManager.instance.primaryFocus?.unfocus();
final removedBoard = boards.removeAt(index);
listKey.currentState!.removeItem(
index,
(context, animation) => SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: BoardListItem(
board: removedBoard,
index: index,
removeItemCallback: removeItemCallback(index),
),
),
duration: const Duration(milliseconds: 500));
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return AnimatedList(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
key: listKey,
initialItemCount: boards.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: BoardListItem(
board: boards[index],
index: index,
removeItemCallback: removeItemCallback(index)));
},
);
}
}
class BoardListItem extends StatefulWidget {
const BoardListItem(
{Key? key,
required this.board,
required this.index,
required this.removeItemCallback})
: super(key: key);
final Board board;
final int index;
final void Function()? removeItemCallback;
#override
State<StatefulWidget> createState() => _BoardListItem();
}
class _BoardListItem extends State<BoardListItem> {
late final TextEditingController bigStakeController;
late final TextEditingController smallStakeController;
late final TextEditingController numberController;
#override
void initState() {
super.initState();
print('initing state');
bigStakeController =
TextEditingController(text: widget.board.bigStake.toString());
smallStakeController =
TextEditingController(text: widget.board.smallStake.toString());
numberController =
TextEditingController(text: widget.board.number.toString());
bigStakeController.addListener(() {
if (bigStakeController.text.isNotEmpty) {
widget.board.bigStake = int.parse(bigStakeController.text);
} else {
widget.board.bigStake = 0;
}
});
smallStakeController.addListener(() {
if (smallStakeController.text.isNotEmpty) {
widget.board.smallStake = int.parse(smallStakeController.text);
} else {
widget.board.smallStake = 0;
}
});
}
#override
void dispose() {
print('disposing these');
bigStakeController.dispose();
smallStakeController.dispose();
numberController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.grey, width: 1.5)),
child: Column(children: [
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: (() {
numberController.text = '1234';
}),
icon: const Icon(Icons.shuffle)),
Text(
'Board ${widget.index + 1}',
style: const TextStyle(fontWeight: FontWeight.w500),
),
IconButton(
onPressed: widget.removeItemCallback,
icon: const Icon(Icons.delete_outline))
],
),
),
Container(
margin: const EdgeInsets.only(top: 15),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color: Colors.grey))),
child: PinCodeTextField(
controller: numberController,
autoDisposeControllers: false,
autoUnfocus: false,
length: 4,
showCursor: true,
enablePinAutofill: false,
keyboardType: TextInputType.number,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
textStyle: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
animationType: AnimationType.scale,
//errorAnimationController: errorController,
appContext: context,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 50,
fieldWidth: 40,
selectedColor: const Color(0xFFB666D2),
inactiveColor: Colors.grey.shade400,
activeColor: const Color(0xFFB666D2)),
onChanged: (value) {
widget.board.number = value;
},
beforeTextPaste: (text) => false,
)),
IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 5), //To manage vertical divider height
child: TextFormField(
controller: bigStakeController,
onTap: () {
if (widget.board.bigStake == 0) {
bigStakeController.clear();
}
},
onEditingComplete: () {
if (widget.board.bigStake == 0) {
bigStakeController.text = '0';
}
FocusManager.instance.primaryFocus?.unfocus();
},
enableInteractiveSelection: false,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
border: InputBorder.none,
isDense: true,
label: Text('Big'),
),
)),
),
const VerticalDivider(
thickness: 1.5,
color: Colors.grey,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: TextFormField(
controller: smallStakeController,
onTap: () {
if (widget.board.smallStake == 0) {
smallStakeController.clear();
}
},
onEditingComplete: () {
if (widget.board.smallStake == 0) {
smallStakeController.text = '0';
}
FocusManager.instance.primaryFocus?.unfocus();
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]'))
],
keyboardType: TextInputType.number,
enableInteractiveSelection: false,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
floatingLabelBehavior: FloatingLabelBehavior.always,
label: Text('Small'),
),
),
),
)
],
),
),
])),
);
}
}
When removing the first item, an unexpected behavior happens where the controller's text is sent to the second item. This does not happen when you remove the second item. (e.g Removing board 2).
Upon checking my data source for my second item, nothing was changed too.
Am I implementing TextEditingControllers into an AnimatedList wrongly? And if so, how do I properly implement it?

Flutter cannot select, copy or paste in EditableText

im trying to use an Editable text widget in Flutter and i cant select, copy, cut and paste don't work with it, i don't know why this is happening I've enabled everything that is related to selecting and copy pasting, but it still doesn't work
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget _backButton() {
return InkWell(
onTap: () {
Navigator.pop(context);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: <Widget>[
Icon(
Icons.clear,
size: 30.0,
color: Color(0xff8729e6),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.white,
child: EditableText(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
copy: true,
selectAll: true,
),
readOnly : false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(
color: Colors.black
),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
backgroundCursorColor: Colors.red,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
selectionColor: Colors.red,
selectionControls: _mainContentSelectionController,
),
)
],
)
),
),
);
}
}
i hope this code is enough, please let me know if you want me to provide more code or if you have any questions, Thank you!
Try Selectable Text instead of simple text widget. more info here.
You can use TextField instead of Editable Text, this code works now.
import 'package:flutter/material.dart';
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: MediaQuery.of(context).size.height * 0.1,
color: Colors.white,
child: TextField(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
cut: true,
copy: true,
selectAll: true,
),
readOnly: false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(color: Colors.black),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
),
)
],
)),
),
);
}
}
As per this answer, EditableText is rarely used.