Flutter - Create a Widget extending TextField with specific values - flutter

Currently working on a way to translate romaji to hiragana directly in my TextField, I'v managed to get something working:
class RomajiTextInput extends StatefulWidget{
final bool mustConvertToKana;
const RomajiTextInput({Key key, this.mustConvertToKana}) : super(key: key);
#override
State<StatefulWidget> createState() => _RomajiTextInputState();
}
class _RomajiTextInputState extends State<RomajiTextInput> {
TextEditingController _titleEditingController;
TextEditingController _hiddenTitleEditingController;
String previousValue = "";
#override
void initState() {
super.initState();
_titleEditingController = new TextEditingController();
_hiddenTitleEditingController = new TextEditingController();
}
#override
void dispose() {
super.dispose();
_titleEditingController.clear();
_hiddenTitleEditingController.clear();
}
void onConversionChanged(String text){
if (widget.mustConvertToKana){
_hiddenTitleEditingController.text = getRomConversion(text, onlyRomaji: false);
String japanese = getJapaneseTranslation(_hiddenTitleEditingController.text, hasSpace: true);
int cursor = getCursorPosition(previousValue, japanese);
setState(() {
_titleEditingController.text = japanese;
_titleEditingController.selection = TextSelection.fromPosition(TextPosition(offset: japanese.length));
previousValue = japanese;
});
}
}
#override
Widget build(BuildContext context) {
return TextField(
controller: _titleEditingController,
onChanged: (text){
onConversionChanged(text);
},
decoration: InputDecoration(
labelText: 'Question',
labelStyle: TextStyle(fontSize: 20),
hintText:
'Enter a question / a word to remember'),
);
}
}
However, I would like to create a widget using these properties but also which can override all the different fields of a TextField such as "decoration", add some logic to the "onChanged" method or even add other functions from the TextField Widget such as "onEditingComplete".
In fact, I would like to be able to do something like this:
RomajiTextInput(
controller: myChildController,
onChanged: (text){print('text');} //And still convert to hiragana thanks to "onConversionChanged"
decoration: InputDecoration(...) //All options that have not been mentioned are kept otherwise just added
)
I bet there is a way to do so but extending the TextField Widget didn't get me anywhere...
Thank in advance :)

Related

Create / Manage dynamic TextEditingControllers

I got a ListView.builder that generates n number of elements and I am looking at adding a controller for each of them. I have seen some approaches of adding a controller to a list of controllers and then access them by the index however I am just wondering how will this impact the performance of the screen if lets say you have 20 controllers? Are there some best practices for this scenario? Should you even go down this line or avoid it?
I suggest to introduce a Widget for all items in list.
Make sure you dispose in a correct place for the performance.
Also I request to store the user entered value with the object of item will help to restore on scrolls.
Eg:
class YourWidget extends StatefulWidget {
const YourWidget({Key? key, required this.item}) : super(key: key);
final YourItem item;
#override
State<YourWidget> createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
final controller = TextEditingController();
#override
void initState() {
super.initState();
controller.text = widget.item.enteredValue;
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: (value){
widget.item.enteredValue = value;
},
...
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
class YourItem {
String? id;
...
String enteredValue = '';
}

Flutter BlocBuilder displays previous state in TextFormField value

I am trying to create a TextFormField with increment and decrement buttons and TextFormField is editable "by hand" as well. But there is a small problem if I use BLoC with this - state "falls" one behind, meaning that when I tap "+" first time nothing changes, but when I tap it the second time it changes its value to 21 (and so on..).
I tried the same implementation with just a regular Text and it works as expected and updating properly.
I'm just wondering if my logic of how I am setting TextFormField is flawed:
Instantiating TextEditingController with default value amount (20);
On "+" tap:
Adding PlusEvent to increment current value
Getting amount value from state
Widget class:
class MyCalculation extends StatefulWidget {
const MyCalculation({Key? key}) : super(key: key);
#override
State<MyCalculation> createState() => _MyCalculationState();
}
class _MyCalculationState extends State<MyCalculation> {
late TextEditingController _controller;
late MyCalcBloc _bloc;
#override
void initState() {
super.initState();
_bloc = context.read();
_controller.text = _bloc.state.amount.toString();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<MyCalcBloc, MyCalcState>(builder: (context, state) {
return MyCustomTextFormField(
controller: _controller,
onChanged: (value) {},
onPlusTap: () {
_bloc.add(PlusEvent());
_bloc.text = '${state.amount}';
},
onMinusTap: () {});
});
}
}
BLoC class:
class MyCalcBloc extends Bloc<MyCalcEvent, MyCalcState> {
MyCalcBloc() : super(const MyCalcState(amount: 20)) {
on<IncrementFromEvent>(_onPlusEvent);
}
void _onPlusEvent(PlusEvent event, Emitter<MyCalcState> emit) {
final newValue = state.amount + 1;
emit(MyCalcState(amount: newValue));
}
}
You should instantiate TextEditingController within BlocProvider, that way you'll get "current" state value displayed in TextFormField.
#override
Widget build(BuildContext context) {
return BlocBuilder<MyCalcBloc, MyCalcState>(builder: (context, state) {
_controller = TextEditingController(text: state.amount.toString());
return MyCustomTextFormField(
controller: _controller,
onChanged: (value) {},
onPlusTap: () {
_bloc.add(PlusEvent());
_bloc.text = '${state.amount}';
},
onMinusTap: () {});
});
}

Change Value of TextFormField when leaving focus

I have a TextFormField that, if the user leaves it, should fill with specific Text.
So for example, if the user edits the field and enters "Test", and I don't want to allow the String "Test", then as soon as the field looses focus it should replace the Text with "Sike!". So something like onChanged, but the event being the loss of Focus, not a change of value, which is what I had so far:
TextFormField(
controller: myController,
onChanged: (value) {
if (value == "Test"){
myController.text = "Sike!";
}
},
.............
.............
One way you can do this is like so.
class _DemoState extends State<Demo> {
final node = FocusNode();
final tc = TextEditingController();
#override
void initState() {
node.addListener(() {
if (!node.hasFocus && tc.text == 'Test') {
tc.text = "Sike!";
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: tc,
focusNode: node,
);
}
}

Setting initial value gotten from Shared Preferences to controller [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I would like to set an initial value to a form field. I am reading in this answer that it can be done like so
TextEditingController _controller = TextEditingController(text: 'initial value');
But I would like to get the initial value from shared preferences, which is async.
How would I do this?
I tried setting the controller value in initial state but that doesn't work
You have two way :
1 ) get text from prefs BEFORE navigate - or construct widget -. So you can get final variable.
2 ) Load text after initState
class Foo extends StatefulWidget {
#override
_FooState createState() {
return _FooState();
}
}
class _FooState extends State<Foo> {
TextEditingController _controller;
//For check text loaded. bool textLoaded;
bool textLoaded;
String text;
#override
void initState() {
textLoaded = false;
super.initState();
}
Future<void> setText() async {
setState(() {
text = "GET TEXT FROM PREF";
_controller = TextEditingController(text: text);
textLoaded = true;
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
textLoaded
? TextField(
controller: _controller,
)
: const CircularProgressIndicator(),
RaisedButton(
onPressed: () {
_controller.clear();
},
child: const Text('CLEAR'),
),
],
);
}
}

Get text before/after cursor, on currently edited line, in TextField

I can print out all text before and after the cursor using this code:
controller = TextEditingController();
final selection = controller.value.selection;
final text = controller.value.text;
print("\nBEFORE\n${selection.textBefore(text)}");
print("\nAFTER\n${selection.textAfter(text)}\n");
How can I use the TextEditingController to find only the text before/after the cursor on the same line as the cursor?
For example, I want to use the TextEditingController to query the text before/after the current position of the cursor so the text before is "lin" and the text after the cursor is "e2 contents" (in the image above).
Full app code that gets all text before/after the cursor (not on the same line as required):
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: TextFieldApp()));
class TextFieldApp extends StatefulWidget {
#override
_TextFieldAppState createState() => _TextFieldAppState();
}
class _TextFieldAppState extends State<TextFieldApp> {
TextEditingController controller;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: TextField(
keyboardType: TextInputType.multiline,
controller: controller,
maxLines: 10,
maxLength: null,
),
);
}
#override
void dispose() {
super.dispose();
}
#override
void initState() {
controller = TextEditingController();
controller.text = 'line 1\nline2 contents\nline3';
controller.addListener(() {
final selection = controller.value.selection;
final text = controller.value.text;
print("\nBEFORE\n${selection.textBefore(text)}");
print("\nAFTER\n${selection.textAfter(text)}\n");
});
super.initState();
}
}
You can split the text by a \n then take the last value from the array.
Similarly the first entry for the text after.
final selection = controller.value.selection;
final text = controller.value.text;
final before = selection.textBefore(text);
final after = selection.textAfter(text);
print(before.split('\n').last);
print(after.split('\n').first);
Note:
If the users selects cont in a line with content line2 contents they would be
line2 //before
ents //after
If you need the text 'after' to also have the 'selected text',
you may add the text 'inside' to the text 'after'.
selection.textInside(text) + after.split('\n').first;