I am trying to pull a list of products from an REST api to show up in a dropdown. Here is my code:
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gateway_device/backend/api/api_services.dart';
import 'package:gateway_device/flutter_flow/flutter_flow_theme.dart';
import 'package:gateway_device/models/products_list_model.dart';
import 'package:gateway_device/routes/route_helper.dart';
import 'package:get/get.dart';
class EditDevicePortWidget extends StatefulWidget {
final String deviceId;
final String portId;
final String publicId;
final String label;
const EditDevicePortWidget(
{Key? key,
required this.deviceId,
required this.portId,
required this.publicId,
required this.label})
: super(key: key);
@override
State<EditDevicePortWidget> createState() =>
_EditDevicePortWidgetState(deviceId, portId, publicId, label);
}
class _EditDevicePortWidgetState extends State<EditDevicePortWidget> {
final String deviceId;
final String portId;
final String publicId;
final String label;
late String dropDownValueSelected = '';
Future<List<TankProduct>> dropDownProduct = ApiService().getAllProducts();
late TextEditingController textController1 = TextEditingController();
late TextEditingController textController2 = TextEditingController();
late TextEditingController textController3 = TextEditingController();
late TextEditingController textController4 = TextEditingController();
@override
void initState() {
super.initState();
String dropDownValueSelected = '';
textController1.text = '';
textController2.text = '';
textController3.text = '';
textController4.text = '';
}
_EditDevicePortWidgetState(
this.deviceId, this.portId, this.publicId, this.label);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
publicId ' - ' label,
style: FlutterFlowTheme.of(context).bodyText1.override(
fontFamily: 'Heebo',
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
actions: [
IconButton(
hoverColor: Colors.transparent,
// borderRadius: 30,
// borderWidth: 1,
iconSize: 40,
icon: Icon(
Icons.close,
color: Colors.black,
size: 30,
),
onPressed: () {
Get.offNamed(RouteHelper.getPortProfile(
deviceId, portId, publicId, label));
},
),
],
iconTheme: IconThemeData(color: Colors.black),
),
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height.h * 1,
decoration: BoxDecoration(color: Colors.white),
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 10, 0, 0),
child: Text(
'Product:',
style: FlutterFlowTheme.of(context)
.bodyText1
.override(
fontFamily: 'Heebo',
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
DropdownButton<String>(
value: dropDownValueSelected,
items: dropDownProduct.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(item,
style: TextStyle(fontSize: 14)),
))
.toList(),
onChanged: (item) => setState(() {
dropDownValueSelected = item!;
}))
],
),
],
),
),
),
);
}
}
The error I am getting is:
The method 'map' isn't defined for the type 'Future'. Try correcting the name to the name of an existing method, or defining a method named 'map'.
The error is from line 121.
Thanks in advance!
CodePudding user response:
ApiService().getAllProducts()
returns a Future
so you need to add await
before ApiService().getAllProducts()
to get List<TankProduct>
that you can later use to show a dropdown.
List<TankProduct> dropDownProduct = [];
@override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
setState((){
dropDownProduct = await ApiService().getAllProducts();
});
}
}
Now you can use the dropDownProduct
list to show dropdown inside the DropdownButton
widget.
DropdownButton<String>(
value: dropDownValueSelected,
items: dropDownProduct.map((item) {
// ...
})
CodePudding user response:
As VincentDR commented, dropDownProduct
is a Future and it should be awaited. There are multiple ways to achieve this, some more better or prettier than others... I would suggest to remove this line:
Future<List<TankProduct>> dropDownProduct = ApiService().getAllProducts();
Since you should not be invoking an async method and/or communicating with an API in your view. I suggest you investigate state handling and separating your app into layers.
But if you want a quick and functional solution, you can use a FutureBuilder to await for the future to complete, it should be something like this:
FutureBuilder(
future: ApiService().getAllProducts(),
builder(BuildContext context, AsyncSnapshot<String> snapshot){
// Validate if snapshot is loading or if there is an error
if(snapshot.hasError){
// Give feedback to the user about error
} else if(!snapshot.hasData){
// Give feedback to the user about loading in progress
} else {
return Row(
mainAxisSize: MainAxisSize.max,
children: [
DropdownButton<String>(
value: dropDownValueSelected,
items: dropDownProduct.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(item,
style: TextStyle(fontSize: 14)),
))
.toList(),
onChanged: (item) => setState(() {
dropDownValueSelected = item!;
}))
],
);
}
}
);