Reputation: 431
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
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
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
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