I'm learning to use the Getx library with Flutter and I have the following problem: the idea is to have a list of TextFields, in which each one is horizontally expandable, that is, its occupied size on the screen is given by the size of the typed word, as the word size increases, the TextField size also increases. If I add a line, through the FloatingActionButton, there is no problem, I can type smoothly, but when I add the second line, the following error occurs:
════════ Exception caught by widgets library ═══════════════════════════════════ The following assertion was thrown building NewLineKeyword(dirty): setState() or markNeedsBuild() called during build.
This GetX 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: GetX<NewLineKeywordController>
controller: null tag: null has builder state: GetXState#ae1d4(controller: Instance of 'NewLineKeywordController') The widget which was currently being built when the offending call was made was: NewLineKeyword dirty The relevant error-causing widget was NewLineKeyword
The image below shows at runtime:
Below is the code for the horizontally expandable TextField:
class NewLineKeyword extends GetView<NewLineKeywordController>{
final Key myKey;
const NewLineKeyword({
Key? key,
required this.myKey,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final newLineKeywordController = Get.put(NewLineKeywordController());
newLineKeywordController.setNewLineSize(myKey, 60.0);
return GetX<NewLineKeywordController>(
builder: (_){
return Row(
children: <Widget>[
const SizedBox(
width: 7.5
),
SizedBox(
width: newLineKeywordController.getNewLineSize(myKey),
child: TextField(
autofocus: true,
minLines: 1,
maxLines: 1,
decoration: const InputDecoration(
constraints: BoxConstraints(minWidth: 15.0),
border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 13.0),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue)
)
),
style: const TextStyle(color: Colors.white, fontSize: 20.0),
onChanged: (String newValue){
// get the exact size of the String
final TextPainter textPainter = TextPainter(
text: TextSpan(text: newValue, style: const TextStyle(fontSize: 20.0)),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
newLineKeywordController.changeNewLineSize(myKey, textPainter.size.width 2.0);
},
onSubmitted: (String newValue){
// get the exact size of the String
final TextPainter textPainter = TextPainter(
text: TextSpan(text: newValue, style: const TextStyle(fontSize: 20.0)),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
newLineKeywordController.changeNewLineSize(myKey, textPainter.size.width 2.0);
}
)
)
]
);
}
);
}
}
Below is the TextField Getx controller code:
class NewLineKeywordController extends GetxController{
final RxMap<Key, double> _mapNewLineSize = <Key, double>{}.obs;
void setNewLineSize(Key key, double size){
_mapNewLineSize[key] = size;
}
void changeNewLineSize(Key key, double newSize){
if(_mapNewLineSize[key] != null){
_mapNewLineSize[key] = newSize;
}
}
double getNewLineSize(Key key){
if(_mapNewLineSize[key] != null){
return _mapNewLineSize[key]!;
}
else{
return 10.0;
}
}
}
Upon the desired keyword, the Keyword widget creates the desired widget(the controller of this widget is empty):
class Keywords extends GetView<KeywordsController>{
final String reservedKeyword;
final Key mykey;
const Keywords({
Key? key,
required this.reservedKeyword,
required this.mykey
}) : super(key: key);
@override
Widget build(BuildContext context){
switch(reservedKeyword){
case "newLine":
return NewLineKeyword(myKey: mykey);
//case "second option":
//return ...
//case "third option":
//return ...
default:
return throw NullThrownError();
}
}
}
The code below corresponds to the Widget for one line:
class Line extends GetView<LineController> {
final Key myKey;
Line({Key? key, required this.myKey}) : super(key: key);
@override
final controller = Get.put(LineController());
@override
Widget build(BuildContext context) {
return GestureDetector(
child: GetX<LineController>(
builder: (_){
return Container(
color: controller.getIsSelected(myKey) ? const Color(0x327a7a7a) : Colors.transparent,
height: 35.0,
child: Row(
children: <Widget>[
const SizedBox(width: 10.0),
controller.getKeywords(myKey)
]
)
);
}
),
onLongPress: (){
controller.changeIsSelected(myKey);
}
);
}
}
Code for the Line widget controller:
class LineController extends GetxController{
final RxMap<Key, Keywords> _mapKeywords = <Key, Keywords>{}.obs;
void setKeywords(Key key, Keywords keyword){
_mapKeywords[key] = keyword;
}
void changeKeywords(Key key, Keywords newKeyword){
if(_mapKeywords[key] != null){
_mapKeywords[key] = newKeyword;
}
}
Keywords getKeywords(Key key){
if(_mapKeywords[key] != null){
return _mapKeywords[key]!;
}
else{
return Keywords(reservedKeyword: "newLine", mykey: key);
}
}
// set, get and change for isSelected
}
The Editor widget below is where the Line list is:
class Editor extends GetView<EditorController> {
const Editor({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
ScrollController scrollCode = ScrollController();
final editorController = Get.put(EditorController());
List<Line> lineList = editorController.getLineList();
return Padding(
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 120.0),
child: GetX<EditorController>(
builder: (_){
return DraggableScrollbar.arrows(
controller: scrollCode,
child: ListView.builder(
controller: scrollCode,
itemCount: lineList.length,
itemBuilder: (BuildContext context, int index){
return lineList[index];
}
)
);
}
)
);
}
}
Editor Getx Controller:
class EditorController extends GetxController{
final RxList<Line> _codeList = <Line>[].obs;
void addLine(Line value){
_codeList.add(value);
}
List<Line> getLineList(){
return _codeList;
}
}
Below is the button to create a new Line in Editor:
class ExpandableFab extends StatelessWidget {
const ExpandableFab({ Key? key }) : super(key: key);
@override
Widget build(BuildContext context) {
final lineController = Get.put(LineController());
final editorController = Get.put(EditorController());
Key mykey;
return FloatingActionButton(
child: const Icon(
Icons.add,
),
onPressed: (){
mykey = GlobalKey();
lineController.setIsSelected(mykey, false);
lineController.setKeywords(mykey, Keywords(reservedKeyword: "newLine", mykey: mykey));
editorController.addLine(Line(myKey: mykey));
}
);
}
}
I didn't understand this error very well because as they are different instances of the same widget, it could be created without any problems. I'm really lost in this problem, how can I create more than one widget in this case?
CodePudding user response:
This is because you are trying to update an observable value while the widget tree is getting built,
this is probable caused because of the method newLineKeywordController.setNewLineSize(myKey, 60.0);
to solve this, you can change your setNewLineSize
method to be:
void setNewLineSize(Key key, double size) async {
await Future.delayed(Duration.zero);
_mapNewLineSize[key] = size;
}