I'm trying to read data from some mock endpoint. Mock endpoint I'm invoking (HTTP GET) is here.
Essentially, the JSON structure is result
> toolList[]
> category
> tools[]
. I'd like to display these items on my page in such a way that the category name is displayed first, then items belonging to that category under it. I am trying to achieve this with ListView.builder but I somehow managed to get some sort of infinite loop and the items keep getting populated until my device freezes.
What I'm trying to achieve:
And finally the Widget:
class OpticsSelectorWidget extends StatefulWidget {
const OpticsSelectorWidget({Key key}) : super(key: key);
@override
_OpticsSelector createState() => _OpticsSelector();
}
class _OpticsSelector extends State<OpticsSelectorWidget> {
PageController pageViewController;
final scaffoldKey = GlobalKey<ScaffoldState>();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: ConfigurationController.getOpticsTools2(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
for (int i = 0; i < toolCategories.length; i++) {
var currentToolCategory = getJsonField(
toolCategories[i],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length - 1; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
}
I'm especially confused about the itemIndex expression. That number I thought would be the item count that I receive from my API call, but I guess I'm mixing something badly.
If it helps, here's the bit where I'm making the API call. But feel free to just grab the JSON your way (from mock response)
static Future<ApiCallResponse> getOpticsTools2() async {
HttpOverrides.global = new MyHttpOverrides();
var client = http.Client();
try {
var response = await client.get(Uri.https('stoplight.io'
, "mocks/ragingtortoise/test/82311857/configuration/tools/optics"));
return createResponse(response, true);
} finally {
client.close();
}
}
static ApiCallResponse createResponse(http.Response response, bool returnBody) {
var jsonBody;
try {
jsonBody = returnBody ? json.decode(response.body) : null;
} catch (_) {}
return ApiCallResponse(jsonBody, response.statusCode);
}
And the return type, which is ApiCallResponse
:
class ApiCallResponse {
const ApiCallResponse(this.jsonBody, this.statusCode);
final dynamic jsonBody;
final int statusCode;
bool get succeeded => statusCode >= 200 && statusCode < 300;
}
Finally adding the screen recording of what's happening, if it helps.
I struggled for so long but clearly, the issue was not passing in the itemCount
argument into the ListView.builder()
method. Also, the outer loop was invalid as now I need to use the actual itemIndex
within the builder. Thanks for pointing out the itemCount all! Here's the fixed code and the solution in case anyone needs it later.
@override
Widget build(BuildContext context) {
final opticsToolsMockResponse = ConfigurationController.getOpticsTools2();
return Scaffold(
backgroundColor: Colors.black,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: opticsToolsMockResponse,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(
itemCount: toolCategories.length,
itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
var currentToolCategory = getJsonField(
toolCategories[itemIndex],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}