最佳实践是执行http请求以接收JSON并在flutter的列表视图中显示它

问题描述

我正在使用Flutter Http包处理Flutter内的网络请求。

到目前为止,我有一个欢迎屏幕和一个网络助手。

欢迎屏幕上有一个带有列表视图和底部栏的支架。

列表视图将为每个列表项显示文本和图像。

这些项目的数据来自HTTP请求。

该请求在我的网络助手类中完成。

在欢迎屏幕中,我有一个从initState()调用函数,该函数会将本地变量设置为从网络帮助程序返回的json,但是一旦我尝试从该变量访问数据,则该函数为null。但是,如果我只打印数据,那么数据就在那里。

我的问题是我已经访问了数据,但是想不到将数据传递到列表视图的方式。

这是我的网络助手类代码的样子

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

class NetworkHelper{
  //Sets up search filter variables
  int year;
  String make;
  String model;
  String condition;
  String combustible;
  String style;
  String color;
  double minPrice;
  double maxPrice;

  Future fetchAlbum() async {
    http.Response response = await http.get('https://jsonplaceholder.typicode.com/todos');

    if (response.statusCode == 200) {
      String data = response.body;
      return jsonDecode(data);
    } else {
      throw Exception('Failed to load album');
    }
  }


}

这是我的欢迎屏幕的样子

import 'package:Flutter/material.dart';
import 'package:components/appBarTitle.dart';
import 'package:components/bottomBarComponent.dart';
import 'package:convex_bottom_bar/convex_bottom_bar.dart';
import 'package:constants.dart';
import 'package:helpers/network_helper.dart';
import 'dart:convert';

class WelcomeScreen extends StatefulWidget {
  static String id = '/welcome_screen';
  @override
  _WelcomeScreenState createState() => _WelcomeScreenState();
}

class _WelcomeScreenState extends State<WelcomeScreen> {

  int _index = 0;

  NetworkHelper http = NetworkHelper();
  dynamic data;
  Future testMethod () async {
    this.data = await http.fetchAlbum();
    print(this.data);
  }

  @override
  void initState() {
    super.initState();
    testMethod();
  }

  @override
  Widget build(BuildContext context) {


    return Scaffold(
      backgroundColor: Colors.white,appBar: AppBar(
        elevation: 0.0,title: AppBarTitle(),),body: Padding(
        padding: const EdgeInsets.all(12.0),child: ListView(
          children: [
            ListBody(
              children: [
                Card(
                  child: Row(
                    children: [
                      Expanded(
                        child: Column(
                          children: [
                            Text('2017 Nissan',style: TextStyle(fontWeight: FontWeight.w300,)),Text('Versa 1.6 SL',style: TextStyle(fontWeight: FontWeight.w100,Padding(
                              padding: const EdgeInsets.all(8.0),child: Text('\$7,998.00',style: TextStyle(fontWeight: FontWeight.w200,],Expanded(
                        child: Image.network('https://via.placeholder.com/450/0000FF',scale: 4,Card(
                  child: Row(
                    children: [
                      Expanded(
                        child: Column(
                          children: [
                            Text('2017 Nissan',)
              ],bottomNavigationBar: ConvexAppBar(
          backgroundColor: Color(0xffe74c3c),items: [
            TabItem(icon: Icons.home,title: 'Home'),TabItem(icon: Icons.map,title: 'discover'),TabItem(icon: Icons.search,title: 'Busca'),TabItem(icon: Icons.person,title: 'Mi Cuenta'),TabItem(icon: Icons.build,title: 'Settings'),initialActiveIndex: 2,//optional,default as 0
          onTap: (int i) => print('click index=$i'),)
    );
  }
}

解决方法

最佳做法是添加一些抽象层。不要在一处做这一切。

为每个传入数据创建一个数据模型。然后将这些对象的列表传递到列表视图。

此示例建立呼叫并创建对象:

Future<List<HowTo>> fetchHowTos() async {
    // get the full web resource path based on environment,etc.
    String callPath = webAPI.getHowToPath();
    // just make the call,don't parse yet
    dynamic response = await makeCall(path: callPath);
    try {
      // this is the object type we want to convert the data into so it's easier to work with throughout your app
      List<HowTo> howTos = [];
      if (response != null) {
        // decode the data and then send each piece into a fromJson method
        dynamic howTosJson = json.decode(response.body);
        howTosJson.forEach((howToJson) {
          howTos.add(HowTo.fromJson(howToJson));
        });
      }
      return howTos;
    } catch (error,stacktrace) {
      logger.error(error,stacktrace,context: 'error parsing how to content');
      return [];
    }
  }

此代码实际上是用来拨打电话的:

Future<dynamic> makeCall({String path}) async {
    try {
      final response = await network.getCall(path: path);

      if (response.statusCode == 200) {
        // If server returns an OK response,parse the JSON.
        return response;
      } else if (response.statusCode == 404) {
        // if no document exists return null.  expected for items without recommendations
        return null;
      } else {
        throw Exception('Failed http call. status: ' +
            response.statusCode.toString());
      }
    } catch (error,context: 'path: $path');
    }
}

这是要在您的应用中创建和使用的对象

class HowTo {
  final String id;
  final String title;
  final int order;

  HowTo({
    this.id,this.title,this.order,});

  factory HowTo.from(HowTo howTo) {
    return HowTo(
      id: howTo.id,title: howTo.title,order: howTo.order,);
  }

  factory HowTo.fromJson(Map<String,dynamic> json) {
    
    return HowTo(
      id: json['id'] ?? '',title: json['Title'] ?? '',order: json['Order'] ?? 0,);
  }
}

将响应从此传递到列表视图包装器

List<HowTo> howTos = await fetchHowTos();
,

您可以在下面复制粘贴运行完整代码
您可以使用FutureBuilderListView.builder
您可以在下面查看工作演示
代码段

Future<List<Payload>> _future;
NetworkHelper http = NetworkHelper();
Future<List<Payload>> testMethod() async {
    var data = await http.fetchAlbum();
    return data;
}

@override
void initState() {
    super.initState();
    _future = testMethod();
}
...  
FutureBuilder(
          future: _future,builder: (context,AsyncSnapshot<List<Payload>> snapshot) {
            ...
                } else {
                  return ListView.builder(
                      shrinkWrap: true,itemCount: snapshot.data.length,itemBuilder: (context,index) {
                        return Card(
                          elevation: 6.0,child: Padding(
                            padding: const EdgeInsets.only(
                                top: 6.0,bottom: 6.0,left: 8.0,right: 8.0),child: ListTile(
                              title: Text(
                                  snapshot.data[index].id.toString()),subtitle: Text(
                                snapshot.data[index].title,),

工作演示

enter image description here

完整代码

import 'package:convex_bottom_bar/convex_bottom_bar.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
// To parse this JSON data,do
//
//     final payload = payloadFromJson(jsonString);

import 'dart:convert';

List<Payload> payloadFromJson(String str) =>
    List<Payload>.from(json.decode(str).map((x) => Payload.fromJson(x)));

String payloadToJson(List<Payload> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Payload {
  Payload({
    this.userId,this.id,this.completed,});

  int userId;
  int id;
  String title;
  bool completed;

  factory Payload.fromJson(Map<String,dynamic> json) => Payload(
        userId: json["userId"],id: json["id"],title: json["title"],completed: json["completed"],);

  Map<String,dynamic> toJson() => {
        "userId": userId,"id": id,"title": title,"completed": completed,};
}

class NetworkHelper {
  //Sets up search filter variables
  int year;
  String make;
  String model;
  String condition;
  String combustible;
  String style;
  String color;
  double minPrice;
  double maxPrice;

  Future<List<Payload>> fetchAlbum() async {
    http.Response response =
        await http.get('https://jsonplaceholder.typicode.com/todos');

    if (response.statusCode == 200) {
      //String data = response.body;
      return payloadFromJson(response.body);
    } else {
      throw Exception('Failed to load album');
    }
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',theme: ThemeData(
        primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,home: WelcomeScreen(),);
  }
}

class WelcomeScreen extends StatefulWidget {
  static String id = '/welcome_screen';
  @override
  _WelcomeScreenState createState() => _WelcomeScreenState();
}

class _WelcomeScreenState extends State<WelcomeScreen> {
  int _index = 0;
  Future<List<Payload>> _future;

  NetworkHelper http = NetworkHelper();

  Future<List<Payload>> testMethod() async {
    var data = await http.fetchAlbum();
    return data;
  }

  @override
  void initState() {
    super.initState();
    _future = testMethod();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.white,appBar: AppBar(
          elevation: 0.0,title: Text("title"),body: Column(
          children: [
            Expanded(
              flex: 1,child: FutureBuilder(
                  future: _future,AsyncSnapshot<List<Payload>> snapshot) {
                    switch (snapshot.connectionState) {
                      case ConnectionState.none:
                        return Text('none');
                      case ConnectionState.waiting:
                        return Center(child: CircularProgressIndicator());
                      case ConnectionState.active:
                        return Text('');
                      case ConnectionState.done:
                        if (snapshot.hasError) {
                          return Text(
                            '${snapshot.error}',style: TextStyle(color: Colors.red),);
                        } else {
                          return ListView.builder(
                              shrinkWrap: true,index) {
                                return Card(
                                  elevation: 6.0,child: Padding(
                                    padding: const EdgeInsets.only(
                                        top: 6.0,child: ListTile(
                                      title: Text(
                                          snapshot.data[index].id.toString()),subtitle: Text(
                                        snapshot.data[index].title,);
                              });
                        }
                    }
                  }),Expanded(
              flex: 1,child: Padding(
                padding: const EdgeInsets.all(12.0),child: ListView(
                  shrinkWrap: true,children: [
                    ListBody(
                      children: [
                        Card(
                          child: Row(
                            children: [
                              Expanded(
                                child: Column(
                                  children: [
                                    Text('2017 Nissan',style: TextStyle(
                                          fontWeight: FontWeight.w300,)),Text('Versa 1.6 SL',style: TextStyle(
                                          fontWeight: FontWeight.w100,Padding(
                                      padding: const EdgeInsets.all(8.0),child: Text('\$7,998.00',style: TextStyle(
                                            fontWeight: FontWeight.w200,],Expanded(
                                child: Image.network(
                                  'https://via.placeholder.com/450/0000FF',scale: 4,Card(
                          child: Row(
                            children: [
                              Expanded(
                                child: Column(
                                  children: [
                                    Text('2017 Nissan',)
                      ],bottomNavigationBar: ConvexAppBar(
          backgroundColor: Color(0xffe74c3c),items: [
            TabItem(icon: Icons.home,title: 'Home'),TabItem(icon: Icons.map,title: 'Discover'),TabItem(icon: Icons.search,title: 'Busca'),TabItem(icon: Icons.person,title: 'Mi Cuenta'),TabItem(icon: Icons.build,title: 'Settings'),initialActiveIndex: 2,//optional,default as 0
          onTap: (int i) => print('click index=$i'),));
  }
}