Issue
I'm new to flutter and I'm trying to get the API data into Flutter. The API contains a List<Maps>
"responseObj"
with key
value
pairs. I want to get that data and show it into DropdownButton
when the app starts i.e. initState()
. I want my output to look like this:
This is the API: http://161.97.96.193:3333/LovLeaveRequest
Below is my code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? value;
List<String> leaveRequest = [];
Future<List> get() async {
var response = await http.get(
Uri.parse("http://161.97.96.193:3333/LovLeaveRequest"),
);
if (response.statusCode == 200) {
final jsonData = jsonDecode(response.body);
return jsonData["responseObj"];
} else {
throw Exception();
}
}
@override
void initState() {
// leaveRequest = get();
get();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: DropdownButton<String>(
hint: Text('Select Type'),
value: this.value,
items: leaveRequest.map((item) {
return DropdownMenuItem(
child: Text(item),
value: item,
);
}).toList(),
onChanged: (value) {
setState(() {
this.value = value;
});
},
),
),
),
);
}
}
The problem I'm getting with the commented line in initState()
is A value of type 'Future<List<dynamic>>' can't be assigned to a variable of type 'List<String>'. Try changing the type of the variable, or casting the right-hand type to 'List<String>'.
Solution
Fetching API responses is a hard part of work. If you work with plain json responses, it's harder to maintain your code and your code becomes Spaghetti Code. So, the simplest way to deal with it is to generate a model class that converts from and to JSON format.
I suggest you to have a look at JSON Serialization.
Also, you should show a circular or linear whatever loading bar while fetching data so that user don't confuse with their app freezing. Here is your code modified by me.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? value;
bool loading = true;
List<ApiResponse> leaveRequest = [];
void get() async {
var response = await http.get(
Uri.parse("http://161.97.96.193:3333/LovLeaveRequest"),
);
if (response.statusCode == 200) {
final jsonData = jsonDecode(response.body);
List<dynamic> data = jsonData["responseObj"];
for (dynamic d in data) {
leaveRequest.add(ApiResponse.fromJson(d));
}
setState(() {
loading = false;
});
} else {
throw Exception();
}
}
@override
void initState() {
get();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
child: Center(
child: loading
? CircularProgressIndicator()
: DropdownButton<String>(
hint: const Text('Select Type'),
value: value,
items: leaveRequest.map((item) {
return DropdownMenuItem(
value: item.value,
child: Text(item.value),
);
}).toList(),
onChanged: (val) {
setState(() {
value = val;
});
},
),
),
),
);
}
}
class ApiResponse {
ApiResponse({
required this.key,
required this.value,
});
int key;
String value;
factory ApiResponse.fromJson(Map<String, dynamic> json) => ApiResponse(
key: json["Key"],
value: json["Value"],
);
Map<String, dynamic> toJson() => {
"Key": key,
"Value": value,
};
}
Here is the bonus part. When calling async methods within initState method, you should call like this
Happy Coding TT
@override
void initState(){
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
*yourAsyncMethod(); });
}
Answered By - Ye Lwin Oo
Answer Checked By - Robin (JavaFixing Admin)