I'm trying to make an example with GetX where I have the following structure:
A HomePage with a list of Widget1, each Widget1 has a list of Widget2.
I'm having two problems:
The following assertion was thrown building Widget1(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
I/flutter (27561): list: 1
I/flutter (27561): list: 2
I/flutter (27561): list: 3
I/flutter (27561): list: 4
HomePage:
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final homeController = Get.put(HomeController());
final widget1Controller = Get.put(Widget1Controller());
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
final mykey = GlobalKey();
controller.addWidget1(Widget1(mykey: mykey));
},
),
body: GetX<HomeController>(
builder: (_) {
return ListView.builder(
itemBuilder: (context, index) {
return controller.getWidget1[index];
},
itemCount: controller.getWidget1.length,
);
},
),
);
}
}
HomePage Controller:
class HomeController extends GetxController {
final RxList<Widget1> _widget1List = <Widget1>[].obs;
void addWidget1(Widget1 newWidget1) {
_widget1List.add(newWidget1);
}
List<Widget1> get getWidget1 {
return _widget1List;
}
}
Widget1:
class Widget1 extends GetView<Widget1Controller> {
final Key mykey;
const Widget1({required this.mykey, super.key});
@override
Widget build(BuildContext context) {
controller.setListWidget2(mykey);
return GetX<Widget1Controller>(
builder: (_) {
return ExpansionTile(
title: Container(
color: Colors.blue,
width: 50.0,
height: 50.0,
),
children: [
ListView.builder(
itemBuilder: (context, index) {
return controller.getListWidget2(mykey)[index];
},
itemCount: controller.getListWidget2(mykey).length,
shrinkWrap: true,
),
IconButton(
onPressed: () {
controller.addWidget2(const Widget2(), mykey);
debugPrint("list: ${controller.getListWidget2(mykey).length}");
},
icon: const Icon(Icons.add),
)
],
);
},
);
}
}
Widget1 Controller:
class Widget1Controller extends GetxController {
final RxMap<Key, List<Widget2>> _mapListWidget2 = <Key, List<Widget2>>{}.obs;
void setListWidget2(Key mykey) {
_mapListWidget2[mykey] = <Widget2>[];
}
void addWidget2(Widget2 newWidget2, Key mykey) {
_mapListWidget2[mykey]!.add(newWidget2);
}
List<Widget2> getListWidget2(Key key) {
if (_mapListWidget2[key] != null) {
return _mapListWidget2[key]!;
} else {
return <Widget2>[];
}
}
}
Widget2:
class Widget2 extends StatelessWidget {
const Widget2({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 300.0,
width: 300.0,
color: Colors.red,
);
}
}
Main:
void main() {
runApp(
const GetMaterialApp(
home: HomePage(),
),
);
}
I am really lost, how can i solve these two problems?
You were facing these issues because some of you have not implemented some of the functionality properly as GetX suggests.
Some of the issues were like putting the controller from initial bindings instead of directly putting it inside the build method.
Here's the working code.
void main() {
runApp(
GetMaterialApp(
initialBinding: InitalBinding(),
home: const HomePage(),
),
);
}
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
var mykey = UniqueKey();
controller.addWidget1(Widget1(mykey: mykey));
},
),
body: GetX<HomeController>(
builder: (logic) {
return ListView.builder(
itemBuilder: (context, index) {
return logic.getWidget1[index];
},
itemCount: logic.getWidget1.length,
);
},
),
);
}
}
class HomeController extends GetxController {
final RxList<Widget1> _widget1List = <Widget1>[].obs;
void addWidget1(Widget1 newWidget1) {
_widget1List.add(newWidget1);
}
List<Widget1> get getWidget1 {
return _widget1List;
}
}
class Widget1 extends GetView<Widget1Controller> {
final Key mykey;
const Widget1({required this.mykey, super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<Widget1Controller>(
builder: (controller) {
return ExpansionTile(
title: Container(
color: Colors.blue,
width: 50.0,
height: 50.0,
),
children: [
ListView.builder(
itemBuilder: (context, index) {
return controller.getListWidget2(mykey)[index];
},
itemCount: controller.getListWidget2(mykey).length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
),
IconButton(
onPressed: () {
if (controller.mapListWidget2.containsKey(mykey)) {
controller.addWidget2(const Widget2(), mykey);
controller.update();
} else {
controller.setListWidget2(mykey);
controller.addWidget2(const Widget2(), mykey);
controller.update();
}
debugPrint("list: ${controller.getListWidget2(mykey).length}");
},
icon: const Icon(Icons.add),
)
],
);
},
);
}
}
class Widget1Controller extends GetxController {
final RxMap<Key, List<Widget2>> mapListWidget2 = <Key, List<Widget2>>{}.obs;
void setListWidget2(Key mykey) {
mapListWidget2[mykey] = <Widget2>[];
}
void addWidget2(Widget2 newWidget2, Key mykey) {
mapListWidget2[mykey]!.add(newWidget2);
}
List<Widget2> getListWidget2(Key key) {
if (mapListWidget2[key] != null) {
return mapListWidget2[key]!;
} else {
return <Widget2>[];
}
}
}
Widget - 2 remains the same.
I have put dependency injection in another file called bindings.
class InitalBinding implements Bindings {
@override
void dependencies() {
Get.put<HomeController>(HomeController(), permanent: true);
Get.put<Widget1Controller>(Widget1Controller(), permanent: true);
}
}