Asad Sheikh
Asad Sheikh

Reputation: 59

Get data from API as List of Dictionary and show it in DropdownButton

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:

enter image description here

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>' can't be assigned to a variable of type 'List'.
Try changing the type of the variable, or casting the right-hand type to 'List'.

Upvotes: 0

Views: 697

Answers (1)

Ye Lwin Oo
Ye Lwin Oo

Reputation: 556

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();   });
}

Upvotes: 1

Related Questions