VanCoon
VanCoon

Reputation: 431

Flutter - List<dynamic>' is not a subtype of type 'Map<String, dynamic>

This is the JSON I am working with from my GET request

[{"id":"1","name":"Item 1","description":"This is the description","imageUrl":"image.png"},{"id":"2","name":"Item 1","description":"This is the description","imageUrl":"image.png"},{"id":"3","name":"Item 1","description":"This is the description","imageUrl":"image.png"},{"id":"4","name":"Item 1","description":"This is the description","imageUrl":"image.png"}]

This is the most recent attempt to get a gridview of data back from the above json with Flutter/Dart.

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('https://somewebsite/pathtoendpoint'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    // return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
    return Album.fromJson(jsonDecode(response.body)[0]);
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}


//-- ALBUM MODEL ----------//
class Album {
  final String id;
  final String? name;
  final String? description;

  const Album({
    required this.id,
    required this.name,
    required this.description,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'],
      name: json['name'],
      description: json['description'],
    );
  }
}
//-- END ALBUM MODEL ----------//

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<Album> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                /* return Text(snapshot.data!.name ?? "");*/
 
                var data = (snapshot.data as List<Album>).toList();
                            
                return GridView.builder(
                itemCount: data.length,//snapshot.data.length,//(snapshot.data as List)[0].length,//
              // - original version   itemCount: snapshot.data!.length,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
            ),
              itemBuilder: (context, int index) {
            return Text("${snapshot.data[index].name}");
              }
          ); 
               // return Text(snapshot.data!.name + snapshot.data!.description);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}
 

This is the most recent error I'm getting and not for lack of trying.

The method '[]' can't be unconditionally invoked because the receiver can be 'null'.

I've tried return Text("${snapshot.data[index]!.name}"); and return Text("${snapshot.data[index].name ?? ""}"); and I am not able to sort this.


Below this line is the original post and attempts


I'm using the following example from docs.flutter.dev to get data from a json endpoint and then to display some text and images. https://docs.flutter.dev/cookbook/networking/fetch-data

I'm getting the following error below which I have attempted to resolve without success.

type 'List' is not a subtype of type 'Map<String, dynamic>'

I tried to resolve this myself by changing the following in the code:

return parsedJson.map((job) => Album.fromJson(job)).toList();

This returned a different error.

type 'List' is not a subtype of type 'FutureOr'

Seems this is a somewhat common error but I've yet to find a solution to this particular use case from the actual flutter example code.

This is the most recent attempt to sort this out:

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import '../models/image_model.dart';


Future<Album> fetchAlbum() async {

  final Uri uri = Uri.https('endpointdomain.com','/listofjson.txt');
  final http.Response response = await http.get(uri);
  dynamic parsedJson = json.decode(response.body);
  parsedJson =  parsedJson.map((job) => Album.fromJson(job)).toList();
  return parsedJson;
}
class Album {
  final String id;
  final String name;
  final String? description;
  final String imageurl;

  const Album({
    required this.id,
    required this.name,
    this.description,
    required this.imageurl,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      imageurl: json['imageUrl'],
    );
  }
}


class ApiTestPage extends StatefulWidget {
  const ApiTestPage({Key, key}) : super(key: key);

  @override
  _ApiTestPageState createState() => _ApiTestPageState();
}

class _ApiTestPageState extends State<ApiTestPage> {

 late Future<Album> futureAlbum;

  TextStyle style = const TextStyle(fontSize: 20.0);

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text("Latest Additions"),
        backgroundColor: Colors.black,
          leading: IconButton(
            icon: const Icon(Icons.arrow_back_ios_new_rounded),
            onPressed: () => Navigator.of(context).pushReplacementNamed('/login'),
          ),
      ),
      body: Center(

          child: GridView.extent(
            primary: false,
            padding: const EdgeInsets.all(16),
            crossAxisSpacing: 10,
            mainAxisSpacing: 10,
            maxCrossAxisExtent: 200.0,
            children: <Widget> [

              FutureBuilder<Album> (
              future: futureAlbum,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  // Should show list of names from JSON retrieved
                  return Text(snapshot.data!.name);

                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }

                // By default, show a loading spinner.
                return const CircularProgressIndicator();
              },
            )
        ],
          )),
    );
  }
}

 

Below is the original code I was working with.

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import '../models/image_model.dart';




Future<Album> fetchAlbum() async {
  final response = await http
      .get(Uri.parse('endpoint url would be here'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    final parsedJson = json.decode(response.body);
    print(parsedJson);


    return parsedJson.map((job) => Album.fromJson(job)).toList();
    return Album.fromJson(parsedJson);
  //  return Album.fromJson(jsonDecode(response.body)); // Returning list not map
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}
class Album {
  final String id;
  final String name;
  final String? description; // some data comes back null so we make this optional
  final String imageurl;

  const Album({
    required this.id,
    required this.name,
    this.description,
    required this.imageurl,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      imageurl: json['imageUrl'],
    );
  }
}


class ApiTestPage extends StatefulWidget {
  const ApiTestPage({Key, key}) : super(key: key);

  @override
  _ApiTestPageState createState() => _ApiTestPageState();
}

class _ApiTestPageState extends State<ApiTestPage> {

  late Future<Album> futureAlbum;

  TextStyle style = const TextStyle(fontSize: 20.0);

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text("Go Back Home"),
        backgroundColor: Colors.black,
          leading: IconButton(
            icon: const Icon(Icons.arrow_back_ios_new_rounded),
            onPressed: () => Navigator.of(context).pushReplacementNamed('/login'),
          ),
      ),
      body: Center(

          child: GridView.extent(
            primary: false,
            padding: const EdgeInsets.all(16),
            crossAxisSpacing: 10,
            mainAxisSpacing: 10,
            maxCrossAxisExtent: 200.0,
            children: <Widget> [

              FutureBuilder<Album>(
              future: futureAlbum,
              builder: (context, snapshot) {
                if (snapshot.hasData) {

                  return Text(snapshot.data!.name);
                  // Need to figure out how to show name and imageurl here still

                } else if (snapshot.hasError) {
                  return Text('${snapshot.error}');
                }

                // By default, show a loading spinner.
                return const CircularProgressIndicator();
              },
            )
        ],
          )),
    );
  }
}

 

Upvotes: 1

Views: 3751

Answers (3)

Prodromos Sarakinou
Prodromos Sarakinou

Reputation: 921

[UPDATE]

The first error it fixed with the change you made

You have to make a function that will get the album

Like this:

  Future<Album> getAlbum() async{
   
    futureAlbum = await fetchAlbum();

    //because you use a FutureBuilder, you're gonna need to get a result
    return futureAlbum;
  }

Also, in your FutureBuilder

future: futureAlbum,

and remove this line from initState

futureAlbum = await fetchAlbum();

Upvotes: 0

Burak Aybi
Burak Aybi

Reputation: 219

You can use "as" to work like Map<String,dynamic>. This query needs to be returned as List

dynamic parsedJson = json.decode(response.body);

(parsedJson as List<dynamic>)
              .map((job) => Album.fromJson(job))
              .toList();

Upvotes: 0

RENE PEREIRA
RENE PEREIRA

Reputation: 1

It seems to me that your parsedJson is a Map<String, dynamic>, so try using List.from(parsedJson.map((job) => Album.fromJson(job))) ou List<Album>.from(parsedJson.map((job) => Album.fromJson(job)))

Upvotes: 0

Related Questions