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'),
),
],
);
}
}
Related
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: () {});
});
}
I wrote logic with edit mode which allows user to make changes in input field, but when edit mode button is clicked again then input need back to value before editing. And there is a problem with that, because everything works fine but console is showing me this error:
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for TextEditingController:
setState() or markNeedsBuild() called during build.
This Form widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: Form-[LabeledGlobalKey<FormState>#bcaba]
state: FormState#65267
The widget which was currently being built when the offending call was made was: ProfileInput
dirty
state: _ProfileInputState#32ea5
I know what this error means, but I can't find a place responsible for this. Could someone explain it to me?
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
User _user = User(
username: "name",
);
String? _tmpUsername;
bool _editMode = false;
void _createTemporaryData() {
_tmpUsername = _user.username;
}
void _restoreData() {
_user.username = _tmpUsername!;
}
void _changeMode() {
if (_editMode)
_restoreData();
else
_createTemporaryData();
setState(() {
_editMode = !_editMode;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () => _changeMode(), child: Text("change mode")),
Form(
key: _formKey,
child: ProfileInput(
editMode: _editMode,
user: _user,
onChangeName: (value) {
_user.username = value;
},
),
),
],
),
);
}
}
class ProfileInput extends StatefulWidget {
final bool editMode;
final User user;
final void Function(String value)? onChangeName;
ProfileInput({
required this.editMode,
required this.user,
required this.onChangeName,
});
#override
_ProfileInputState createState() => _ProfileInputState();
}
class _ProfileInputState extends State<ProfileInput> {
TextEditingController _nameController = TextEditingController();
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_nameController.text = widget.user.username;
return TextFormField(
onChanged: widget.onChangeName,
controller: _nameController,
enabled: widget.editMode,
);
}
}
Put the following line in the initState or use addPostFrameCallback.
_nameController.text = widget.user.username; // goes into initState
initState
#override
void initState() {
super.initState();
_nameController.text = widget.user.username;
}
addPostFrameCallback
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 1
SchedulerBinding.instance.addPostFrameCallback((_) {
_nameController.text = widget.user.username;
}); // 2
// use either 1 or 2.
// rest of the code, return statement.
}
Calling text setter on _nameController would notify all the listener and it's called inside the build method during an ongoing build that causes setState() or markNeedsBuild() called during build.
From Documentation:
Setting this will notify all the listeners of this TextEditingController that they need to update (it calls notifyListeners). For this reason, this value should only be set between frames, e.g. in response to user actions, not during the build, layout, or paint phases.
What is the solution of initializing things inside consumerWidget as because the initState method is not overridable here?
Riverpod v2.1.3
You can use ConsumerStatefulWidget and ConsumerState
final helloWorldProvider = Provider((_) => 'Hello world');
class RiverpodExample extends ConsumerStatefulWidget {
const RiverpodExample({super.key});
#override
ConsumerState<RiverpodExample> createState() => _RiverpodExampleState();
}
class _RiverpodExampleState extends ConsumerState<RiverpodExample> {
#override
void initState() {
super.initState();
final value = ref.read(helloWorldProvider);
print(value); // Hello world
}
#override
Widget build(BuildContext context) {
final value = ref.watch(helloWorldProvider);
return Text(value); // Hello world
}
}
I'm not totally sure how to answer your question as I have not worked with ConsumerWidget. I'd presume the idea is to keep most of your state in providers.
However, I would like to recommend using hooks_riverpod alongside flutter_hooks (same developer).
This makes keeping state local to the widget simple and also provides easy access to providers.
For example:
class Example extends HookWidget {
const Example({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final test = useProvider(Test.provider());
final controller = useTextEditingController();
final loading = useState(false);
final buttonText = useState('Change me!');
return Column(
children: [
TextField(controller: controller),
if (!loading) RaisedButton(
onPressed: () async {
loading.value = true;
await Future.delayed(const Duration(seconds: 1));
buttonText.value = controller.text;
loading.value = false;
}
child: Text(buttonText.value),
),
if (loading) const CircularProgressIndicator(),
// Do something with providers, etc.
],
),
);
}
Just a quick example, but there are plenty of resources (flutter_hooks, hooks_riverpod) to help you along. Also, check out examples from the developer on riverpod hooks usage.
I may be late but with the upcoming 1.0.0 of Riverpod you will be able to use https://pub.dev/documentation/flutter_riverpod/1.0.0-dev.2/flutter_riverpod/ConsumerStatefulWidget-class.html and https://pub.dev/documentation/flutter_riverpod/1.0.0-dev.2/flutter_riverpod/ConsumerState-class.html, which does exactly what you want.
I have a stateful widget LetterButton()
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(widget.caption),
onPressed: onChanged,
color: colors[currentIndex],
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
);
}
In my main.dart file I declare an array of LetterButtons
List<LetterButton> buttonArray;
which I initialize and fill during initState() via the method
void makeButtons() {
for (var letter in alphabet) {
buttonArray.add(
LetterButton(letter),
);
}
}
The buttons in the list are then displayed in the UI
Wrap(
children: buttonArray,
)
How can I change the value of currentIndex (an int in
class LetterButtonState extends State<LetterButton>) or otherwise change all the buttons to the same color from main.dart?
NOTE: I asked a similar question a few days ago, but the answer was a little above my current knowledge, as are responses I've seen to similar Q's here on SO. I have a little understanding of callbacks, and experimented a little with the provider package, but there's such a variety of answers and info available online that it's hard for me to even know what I don't know to be able to answer my question :-)
Create stateful widget with state as public access, so that you can access outside of the package and provide key to constructor. So that you can refer key and get can get state to change value. See the following example
class LetterButton extends StatefulWidget {
LetterButton({GlobalKey key}) : super(key: key);
#override
LetterButtonState createState() => LetterButtonState();
}
class LetterButtonState extends State<LetterButton> {
int value = 0;
//this public method is to update int value
setValue(int value) {
setState(() {
this.value = value;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(value.toString()),
);
}
}
//In Main.dart
GlobalKey<LetterButtonState> _buttonStateKey = GlobalKey();
//while creating widget
LetterButton(key:_buttonStateKey)
//in onTapCallback you can call to update value
_buttonStateKey.currentState?.setValue(10);
Just send your created function to new class by parameter and the new class should be Constarcter with Function lThen you can call the function from a new class.
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 :)