I m working on a web project using Flutter web, I understand currently Flutter web is only in beta version.
Basically I m implementing a web code editor using textfield, If the user press TAB key I want to make an indent as usual. However when I press tab it either move to next element (let say I have a button below the textarea) or it do not indent at all. Anyone know how to solve this?
example code below :
Widget build(BuildContext context) {
return Container(
color: Colors.black87,
child: Padding(
padding: EdgeInsets.all(8.0),
child:TextField(
controller: controller,
onEditingComplete: (){print('editing complete');},
onTap: (){},
onChanged: (text){print(text);},
style: TextStyle(fontStyle: FontStyle.italic,color: Colors.white),
maxLines: 20,
autofocus: true,
decoration: InputDecoration.collapsed(hintText: "write code for the formation"),
),
)
);
}
}
I've accomplished adding tabs with something like this:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class InsertTabIntent extends Intent {
const InsertTabIntent(this.numSpaces, this.textController);
final int numSpaces;
final TextEditingController textController;
}
class InsertTabAction extends Action {
#override
Object invoke(covariant Intent intent) {
if (intent is InsertTabIntent) {
final oldValue = intent.textController.value;
final newComposing = TextRange.collapsed(oldValue.composing.start);
final newSelection = TextSelection.collapsed(
offset: oldValue.selection.start + intent.numSpaces);
final newText = StringBuffer(oldValue.selection.isValid
? oldValue.selection.textBefore(oldValue.text)
: oldValue.text);
for (var i = 0; i < intent.numSpaces; i++) {
newText.write(' ');
}
newText.write(oldValue.selection.isValid
? oldValue.selection.textAfter(oldValue.text)
: '');
intent.textController.value = intent.textController.value.copyWith(
composing: newComposing,
text: newText.toString(),
selection: newSelection,
);
}
return '';
}
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController textController;
#override
void initState() {
super.initState();
textController = TextEditingController();
}
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Actions(
actions: {InsertTabIntent: InsertTabAction()},
child: Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.tab):
InsertTabIntent(2, textController)
},
child: TextField(
controller: textController,
textInputAction: TextInputAction.newline,
maxLines: 30,
keyboardType: TextInputType.multiline,
),
),
),
],
),
),
);
}
}
Related
I have a TextField. I want its text not to be empty. (so I want to know if the text is empty)
I have tried using the following code, but it doesn't work:
controller.text.trim().isEmpty()
My code:
TextFormField(
controller: controller,
),
controller.text.trim().isEmpty()
How to continuously get whether the TextField's text is empty in Flutter? I would appreciate any help. Thank you in advance!
full example:
code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
String _text = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Container(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_text),
const SizedBox(height: 20),
TextField(
controller: _controller,
onChanged: (value) {
setState(() {
_text = value;
});
},
decoration: const InputDecoration(
hintText: 'Enter text',
),
),
// submit
ElevatedButton(
onPressed: _text.isEmpty
? null
: () {
setState(() {
_text = _controller.text;
});
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
It can be done without any temporary variable using ValueListenableBuilder
After some research figured out
controller.text by itself is not listenable
TextEditingController extends ValueNotifier<TextEditingValue> i.e you can use ValueListenableBuilder from material package to listen to text changes.
Code:
class _MyWidgetState extends State<MyWidget> {
late TextEditingController textEditingController;
#override
void initState() {
textEditingController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextField(
controller: textEditingController,
),
ValueListenableBuilder<TextEditingValue>(
valueListenable: textEditingController,
builder: (context, value, child) {
return ElevatedButton(
onPressed: value.text.isNotEmpty ? () {} : null,
child: const Text('I am disabled when text is empty'),
);
},
),
],
),
),
);
}
}
Without text:
With text:
You can add listener to your TextEditingController and call setState to update the UI.
late TextEditingController controller = TextEditingController()..addListener(() {
setState((){}); // to update the ui
});
The place you will use controller.text.trim().isEmpty() will show the updated state.
Example
class Test extends StatefulWidget {
const Test({super.key});
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
late TextEditingController controller = TextEditingController()
..addListener(() {
setState(() {}); // to update the ui
});
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: controller,
),
ElevatedButton(
onPressed: controller.text.trim().isEmpty ? null : () {},
child: Text("Button"))
],
);
}
}
i am trying to implement text field with showing symbol of height at the right of the text. for example when user enter any input the TextField must be like:
180 cm
as you see above cm should be appear whenever any text changed and shouldn't be removable.
i am trying to achieve it by using following library but could success. any suggestion?
https://pub.dev/packages/flutter_masked_text2
here is what I tried:
var heightController = MaskedTextController(
mask: '000 cm',
);
you can try this
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Postfix Text Controller',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _textController = TextEditingController();
final String _userPostfix = " cm";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text("Postfix Text Controller"),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
controller: _textController,
onChanged: (value) {
if (value == _userPostfix) {
_textController.text = "";
return;
}
value.endsWith(_userPostfix)
? _textController.text = value
: _textController.text = value + _userPostfix;
_textController.selection = TextSelection.fromPosition(
TextPosition(
offset: _textController.text.length -
_userPostfix.length));
},
decoration: const InputDecoration(
labelText: "Height",
border: OutlineInputBorder(),
),
),
],
),
),
),
);
}
}
You can try this
TextField(
controller: _textController,
keyboardType: TextInputType.number,
onChanged: (val) {
_textController.text =
val.replaceAll("c", "").replaceAll("m", "") + " cm";
_textController.selection = TextSelection.fromPosition(
TextPosition(
offset: _textController.text.length - 3));
},
),
What this code does is print the position of the cursor in the text when you click on it.
TextEditingController controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
onTap: () {
int pos = controller.selection.start;
print(
"Position: $pos",
);
},
),
);
}
But the problem is that this is for web so I need it to say the position of the cursor whenever it moves not only when I tap on it.
I mean if you move the cursor with the keyboard it should also print the position.
I already tried with GestureDetector and Focus and nothing. Any idea what I can use?
UPDATE:
The solution I found was the following and it works perfectly, for what I need:
TextEditingController controller = TextEditingController();
int cursorPosition = 0;
#override
void initState() {
controller.addListener(() {
setState(() {
cursorPosition = controller.selection.base.offset;
print("cursorPosition: $cursorPosition");
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
),
);
}
Full Code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController controller = TextEditingController();
int cursorPosition = 0;
#override
void initState() {
controller.addListener(() {
setState(() {
cursorPosition = controller.selection.base.offset;
print("cursorPosition: $cursorPosition");
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
),
);
}
}
The solution I found was the following and it works perfectly, for what I need:
TextEditingController controller = TextEditingController();
int cursorPosition = 0;
#override
void initState() {
controller.addListener(() {
setState(() {
cursorPosition = controller.selection.base.offset;
print("cursorPosition: $cursorPosition");
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
),
);
}
Full Code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController controller = TextEditingController();
int cursorPosition = 0;
#override
void initState() {
controller.addListener(() {
setState(() {
cursorPosition = controller.selection.base.offset;
print("cursorPosition: $cursorPosition");
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: TextField(
controller: controller,
keyboardType: TextInputType.multiline,
maxLines: null,
),
);
}
}
onChanged: (value) {
int pos = controller.selection.start;
print(
"Position: $pos",
);
},
I am trying to develop a form using flutter and I need to change the border of my cupertinotextfield when the user focused on it.
You can copy paste run full code below
You can use onFocusChange of FocusScope to check focus and change BoxDecoration to what you need
code snippet
FocusScope(
child: Focus(
onFocusChange: (focus) {
if (focus) {
setState(() {
boxSetting = boxHasFocus;
});
} else {
setState(() {
boxSetting = defaultBoxSetting;
});
}
},
child: CupertinoTextField(
decoration: boxSetting, controller: _textController)))
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _textController;
BoxDecoration boxSetting;
BoxDecoration defaultBoxSetting = CupertinoTextField().decoration;
BoxDecoration boxHasFocus = BoxDecoration(
border: Border.all(color: Color(0xFFFFFF00), width: 0.5),
color: Color(0xFF9E9E9E),
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular((20.0)),
);
#override
void initState() {
_textController = TextEditingController(text: '');
boxSetting = defaultBoxSetting;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FocusScope(
child: Focus(
onFocusChange: (focus) {
if (focus) {
setState(() {
boxSetting = boxHasFocus;
});
} else {
setState(() {
boxSetting = defaultBoxSetting;
});
}
},
child: CupertinoTextField(
decoration: boxSetting, controller: _textController))),
],
),
),
);
}
}
When tapping a textformfield in a pushed screen, the constructor of the screen is called again and the textformfield loses its value. Also, I think that every change happens in that screen causes its constructor to be called again, and I don't know the reason at all.
Here is a sample code that generates the error:
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Hello',
style: TextStyle(color: Colors.black, fontSize: 30.0),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => NextScreen(Bloc())));
},
child: Icon(Icons.add),
),
);
}
}
And here is the screen to be pushed
class NextScreen extends StatefulWidget {
final _bloc;
NextScreen(this._bloc);
#override
_NextScreenState createState() => _NextScreenState();
}
class _NextScreenState extends State<NextScreen> {
#override
void dispose() {
widget._bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(100.0),
child: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(Icons.arrow_back),
iconSize: 20.0,
),
),
StreamBuilder<String>(
stream: widget._bloc.stream,
builder: (context, snapshot) {
return TextFormField(
controller: widget._bloc.controller,
onFieldSubmitted: widget._bloc.submitData(),
decoration: InputDecoration(
hintText: 'Enter your name..',
errorText: snapshot.data,
),
);
})
],
),
);
}
}
A bloc that validates the user input
class Bloc {
TextEditingController _controller;
TextEditingController get controller => _controller;
BehaviorSubject<String> _subject;
BehaviorSubject<String> _validatorSubject;
Stream<String> get stream => _validatorSubject.stream;
void submitData() {
_subject.sink.add(controller.text);
}
void _validate(String text) {
if (!RegExp(r'[0-9]').hasMatch(text)) {
_validatorSubject.sink.add('numbers only');
} else {
_validatorSubject.sink.add(null);
}
}
Bloc() {
_controller = TextEditingController();
_subject = BehaviorSubject<String>();
_validatorSubject = BehaviorSubject<String>();
_subject.stream.listen(_validate);
}
void dispose() {
_subject.close();
_validatorSubject.close();
}
}
Opening and closing a keyboard will rebuild the whole screen.
The real culprit here is the textController :
controller: widget._bloc.controller,
The solution which worked for me is to remove this line.
Also to get and validate the changed text you can use onChanged in the text field, which return a String.
Like this :
.
.
.
return TextFormField(
onChanged: widget._bloc.submitData,
decoration: InputDecoration
.
.
.
And you submitData() method would go like this :
void submitData(String data) {
_subject.sink.add(data);
}