How to pass data retrieved from a database using Json from one screen to another in a ListView

Hi everyone, I can’t pass data from a json to another screen. My goal is to retrieved, let say scientificName of a fish and using that called data from a database to output some other data

The expected result for now is to display the scientific name as tittle on the app
This is the error I’m getting “Another exception was thrown: type ‘_InternalLinkedHashMap<String, dynamic>’ is not a subtype of type ‘Fish’”

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

class Fish {
  final String scientificName;
  final String species;
  final String picName;
  Fish(this.scientificName, this.species, this.picName);
}

class Database extends StatefulWidget {
  String get title => "Database";

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

class _DatabaseState extends State<Database> {
  List<dynamic> fishList = List<dynamic>();
  getAllFish() async {
    var response =
        await http.get("http://192.168.43.202/maupoisson/getfish.php");
    if (response.statusCode == 200) {
      setState(() {
        fishList = json.decode(response.body);
      });
      print(fishList);
      return fishList;
    }
  }

  void initState() {
    super.initState();
    getAllFish();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: ListView.builder(
            itemCount: fishList.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: CircleAvatar(
                  radius: 30.0,
                  backgroundImage: ExactAssetImage(
                      "assets/images/$fishList[index]['pic_name']"),
                ),
                title: Text(fishList[index]['scientific_name']),
                subtitle: Text(fishList[index]['species']),
                onTap: () {
                  Navigator.push(
                      context,
                      new MaterialPageRoute(
                          builder: (context) =>
                              DetailScreen(fish: fishList[index])));
                },
              );
            }));
  }
}

class DetailScreen extends StatelessWidget {
  final Fish fish;

  DetailScreen({Key key, @required this.fish}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(fish.scientificName),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(fish.species),
      ),
    );
  }
}

Hello, I think you are not creating a list of Fish objects, just parsing the json response body into a Dart Map. But your code then expects them to be actual Fish objects.

I’d try adding a fromJson factory function in Fish class, like:

class Fish {
  final String scientificName;
  final String species;
  final String picName;
  Fish(this.scientificName, this.species, this.picName);

  factory Fish.fromJson(Map<String, dynamic> json) {
    return Fish(
      scientificName: json['scientificName'],
      species: json['species'],
      picName: json['picName'],
    );
  }
}

then use it in getAllFish(), like:

class _DatabaseState extends State<Database> {
  List<dynamic> fishList = List<dynamic>();
  getAllFish() async {
    var response =
        await http.get("http://192.168.43.202/maupoisson/getfish.php");
    if (response.statusCode == 200) {

        fishList = json.decode(response.body);

        # create a list of Fish objects from the json reponse
        fishList.map((json) => Fish.fromJson(json)).toList();

        print(fishList);
        return fishList; 
    }
  }

[..]

Hi thanks for the help. I tried your solution but now, I’m getting a blank screen where the Listview should be.

The data retrieved by the json is showned in the terminals but nothing on the app screen

oh I see, i just edited your code here, no test at all, my bad… maybe it is because I removed the call to setState in your response handler…

setState(() {
    fishList = json.decode(response.body);
});

because I saw you were getting the list into initState()… but it’s a while I do not write actual Flutter code so… I’m likely wrong there.

Sorry to impose on your time, it works the Listview is back but when I click on one of the items, it is throwing this error:

The following _TypeError was thrown building Builder(dirty):
I/flutter (12598): type ‘_InternalLinkedHashMap<String, dynamic>’ is not a subtype of type ‘Fish’

It’s quite difficult to perform such “blind debug” (even more than a blind go game ;), I guess there are still issues with the state of the widget or the flishList’s items type, or some Dart’s typing quircks when you refer to it/them … If you can’t resolve, try posting the updated code, I’ll be happy to help if I can.

Bellow is the updated code with your suggestions

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

class Fish {
  final String scientificName;
  final String species;
  final String picName;
  Fish({this.scientificName, this.species, this.picName});
  factory Fish.fromJson(Map<String, dynamic> json) {
    return Fish(
      scientificName: json['scientificName'],
      species: json['species'],
      picName: json['picName'],
    );
  }
}

class Database extends StatefulWidget {
  String get title => "Database";

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

class _DatabaseState extends State<Database> {
  List<dynamic> fishList = List<dynamic>();
  getAllFish() async {
    var response =
        await http.get("http://192.168.43.202/maupoisson/getfish.php");
    if (response.statusCode == 200) {
      setState(() {
        fishList = json.decode(response.body);
        fishList.map((json) => Fish.fromJson(json)).toList();
      });
      print(fishList);
      return fishList;
    }
  }

  void initState() {
    super.initState();
    getAllFish();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: ListView.builder(
            itemCount: fishList.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: CircleAvatar(
                  radius: 40.0,
                  backgroundImage: ExactAssetImage(fishList[index]['pic_name']),
                ),
                title: Text(fishList[index]['scientific_name']),
                subtitle: Text(fishList[index]['species']),
                onTap: () {
                  Navigator.push(
                      context,
                      new MaterialPageRoute(
                          builder: (context) =>
                              DetailScreen(fish: fishList[index])));
                },
              );
            }));
  }
}

class DetailScreen extends StatelessWidget {
  final Fish fish;

  DetailScreen({Key key, @required this.fish}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(fish.scientificName),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(fish.species),
      ),
    );
  }
}

in the ListView.builder

you are handling a Fish object,

class Fish {
  final String scientificName;
  final String species;
  final String picName;
..

I guess you cannot access its instance variables with the [ ] operator, like i.e.
title: Text(fishList[index]['scientific_name']), (I’m seeing a suspicious json attribute name there) but you should get them with (i.e.) fishList[index].scientificName, like you are doing in DetailScreen.

I put together a test app, (no pictures, no http, the response.body is an hardcoded json string) , and seems to work.

Test code:

import 'dart:convert';

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Database(title: 'Database'),
    );
  }
}

class Fish {
  final String scientificName;
  final String species;
  final String picName;
  Fish({this.scientificName, this.species, this.picName});
  factory Fish.fromJson(Map<String, dynamic> json) {
    return Fish(
      scientificName: json['scientificName'],
      species: json['species'],
      picName: json['picName'],
    );
  }
}

class Database extends StatefulWidget {
  Database({Key key, this.title}) : super(key: key);
  final String title;

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

class _DatabaseState extends State<Database> {
  List<dynamic> fishList;
  getAllFish() {
    var respBody =
        "[{\"scientificName\": \"Thunnus alalunga\", \"species\": \"Albacore\", \"picName\": \"\"}]";

    setState(() {
      var _fishList = json.decode(respBody);
      fishList = _fishList.map((json) => Fish.fromJson(json)).toList();
    });
    print(fishList);
    return fishList;
  }

  void initState() {
    super.initState();
    getAllFish();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: ListView.builder(
            itemCount: fishList.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: CircleAvatar(
                  radius: 40.0,
                  backgroundImage: ExactAssetImage(fishList[index].picName),
                ),
                title: Text(fishList[index].scientificName),
                subtitle: Text(fishList[index].species),
                onTap: () {
                  Navigator.push(
                      context,
                      new MaterialPageRoute(
                          builder: (context) =>
                              DetailScreen(fish: fishList[index])));
                },
              );
            }));
  }
}

class DetailScreen extends StatelessWidget {
  final Fish fish;

  DetailScreen({Key key, @required this.fish}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(fish.scientificName),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(fish.species),
      ),
    );
  }
}

hth

P.s. this a a side consideration about taxonomy and nomenclature.

The scientific name is made of the genus (Upcased) and the species (downcased) names, when you refer to a species you use the full sci-name or its common name, which is one of many many differents names in differents languages.
So, the Fish.species instance variable is IMHO not well named. I myself have used it to store the common name of the Thunnus alalunga in my test code…

If the json attributes you receive in the reposnse of the http request have another spelling, i.e. they are in snake_case, you will change them accordingly, like:

factory Fish.fromJson(Map<String, dynamic> json) {
    return Fish(
      scientificName: json['scientific_name'],
      species: json['species'],
      picName: json['pic_name'],
    );

(in my test code I simply wrote them the same of Fish’s instance vars.)

It works!!! Thanks you very much for your precious help and time

1 Like

Thanks for this side notes. To be honest, I took those heading name from a fish database that store all the data of local fishes in my country. Either, I picked the column name wrongfully or their column heading are swapped.