flutter之從零開始搭建(三)之 網路請求

codelang發表於2018-07-06

flutter從零開始搭建系列:


專案還是在原來的基礎上搭建,具體的可以看上面的連線

這次,我們來介紹下網路請求,並且將請求到的資料設定到ListView列表中。老規矩,先來看下效果圖

image

頁面看起來不錯吧,在動手之前還是得說一下,首頁資料來自wanandroid提供,畢竟用了別人的東西就得標明。

實戰

flutter請求網路有兩種,一種是http請求,一種是HttpClient請求,下面來分別來使用一下。

http方式

在使用http方式請求網路時,需要匯入http包

//匯入網路請求相關的包
import 'package:http/http.dart' as http; 

void _pullNet()  {
    http.get("http://www.wanandroid.com/project/list/1/json?cid=1")
        .then((http.Response response) {
             var convertDataToJson = JSON.decode(response.body);
             convertDataToJson = convertDataToJson["data"]["datas"];
             //列印請求的結果
             print(convertDataToJson);
             //更新資料
             setState(() {
                data = convertDataToJson;
             });
    });
 }
複製程式碼

httpClient方式

需要匯入httpClient包

import 'dart:io';

void _httpClient() async {
    var responseBody;

    var httpClient = new HttpClient();
    var request = await httpClient.getUrl(
        Uri.parse("http://www.wanandroid.com/project/list/1/json?cid=1"));

    var response = await request.close();
    //判斷是否請求成功
    if (response.statusCode == 200) {
      //拿到請求的資料
      responseBody = await response.transform(utf8.decoder).join();
      //解析json,拿到對應的jsonArray資料
      var convertDataToJson = jsonDecode(responseBody)["data"]["datas"];
      //更新資料
      setState(() {
        data = convertDataToJson;
      });
      
    } else {
      print("error");
    }
  }

複製程式碼

知道了網路請求的概念,那麼,我們先來寫下介面

ListView介面佈局

開啟HomePage,

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new HomeState();
  }
}

 @override
 Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      body: new ListView(
          children: <Widget>[
            _getItem2(),
            _getItem2()
          ]),
    );
  }


  Widget _getItem2() {
    return new Card(child: new Padding(
      padding: const EdgeInsets.all(10.0), child: _getRowWidget2(),),
      elevation: 3.0,
      margin: const EdgeInsets.all(10.0),);
  }


  Widget _getRowWidget2() {
    return new Row(children: <Widget>[
      new Flexible(
          flex: 1,
          fit: FlexFit.tight, //和android的weight=1效果一樣
          child: new Stack(children: <Widget>[
            new Column(children: <Widget>[
              new Text("title".trim(),
                  style: new TextStyle(color: Colors.black, fontSize: 20.0,),
                  textAlign: TextAlign.left),
              new Text("desc", maxLines: 3,)
            ],)
          ],)
      ),
      new ClipRect(child: new FadeInImage.assetNetwork(
        placeholder: "images/ic_shop_normal.png",
        image: "images/ic_shop_normal.png",
        width: 50.0,
        height: 50.0,
        fit: BoxFit.fitWidth,),),
    ],);
  }

複製程式碼

效果如下

image

ListView感覺看起來像是Android中的ScrollView+LineaLayout.vertical。

flutter的佈局其實是個特別頭疼的問題,widget特別多,沒有android那麼方便,也沒有react-native的flex佈局方便,迷之縮排更讓人想刪除widget都變得特別的困難,所以,在做佈局這部分,我們儘可能的將widget做成分割出來,做成一個個的方法widget,然後組合起來。

資料填充

我們看到ListView接收的是一個widget陣列,後臺返回給我們jsonArray資料的時候,我們完全可以使用map來遍歷資料,然後返回widget給Listview的children。

flutter是有生命週期的,大致生命週期可以分為

  • initState : 初始化widget的時候呼叫,只會呼叫一次。
  • build : 初始化之後開始繪製介面,當setState觸發的時候會再次被呼叫
  • didUpdateWidget : 當觸發setState時,會被呼叫
  • dispose : 頁面銷燬的時候呼叫

如果把他們和react-native進行分類看的話,下面是個對比

flutter react native
initState Mount等函式
didUpdateWidget update等函式
dispose Unmount等函式

所以,我們在請求網路的時候,將資料請求放在initState進行處理,下面貼出程式碼。

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //匯入網路請求相關的包


class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new HomeState();
  }
}

class HomeState extends State<HomePage> {
 //資料來源
  List data;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _pullNet();
  }
 
  void _pullNet() async {
    await http.get("http://www.wanandroid.com/project/list/1/json?cid=1")
        .then((http.Response response) {
      var convertDataToJson = JSON.decode(response.body);
      convertDataToJson = convertDataToJson["data"]["datas"];

      print(convertDataToJson);

      setState(() {
        data = convertDataToJson;
      });
    
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      body: new ListView(
          children:  _getItem() ),
    );
  }
  
   List<Widget> _getItem() {
    return data.map((item) {
      return new Card(child: new Padding(
        padding: const EdgeInsets.all(10.0), child: _getRowWidget(item),),
        elevation: 3.0,
        margin: const EdgeInsets.all(10.0),);
    }).toList();
  }
  
 
  Widget _getRowWidget(item) {
    return new Row(children: <Widget>[
      new Flexible(
          flex: 1,
          fit: FlexFit.tight, //和android的weight=1效果一樣
          child: new Stack(children: <Widget>[
            new Column(children: <Widget>[
              new Text("${item["title"]}".trim(),
                  style: new TextStyle(color: Colors.black, fontSize: 20.0,),
                  textAlign: TextAlign.left),
              new Text("${item["desc"]}", maxLines: 3,)
            ],)
          ],)
      ),
      new ClipRect(child: new FadeInImage.assetNetwork(
        placeholder: "images/ic_shop_normal.png",
        image: "${item['envelopePic']}",
        width: 50.0,
        height: 50.0,
        fit: BoxFit.fitWidth,),),
    ],);
  }
複製程式碼

然後我們來看下效果圖

image

相信大家看到了一閃而過的紅色報警圖,雖然不影響最後的顯示效果,但是,我們必須得去處理。

在控制檯中,我看到了這樣的一句異常

The method 'map' was called on null
複製程式碼

看到map我們這才煥然大悟,因為網路請求是非同步的,當前介面因為要執行build介面的繪製,導致我們在_getItem中map遍歷data資料時是個空值,然後再非同步請求成功後,setState又重新給data賦了值,然後觸發了介面重新繪製,這時候,map遍歷是有值,然後就出現了一下會出現異常,然後又好了的原因。

我們得想個辦法,並且能優雅的去解決這個問題,對了,我們只需要在ListView中對data進行判空處理不就行了嗎,如果為空的話,我們給他設定一個預載入頁面,如果不為空的話,直接就當前現在的這套流程。

ok,思路有了,開始幹吧

直接看build方法進行更改

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      body: new ListView(
          children: data != null ? _getItem() : _loading()),
    );
  }
  
  //預載入佈局
  List<Widget> _loading() {
    return <Widget>[
      new Container(
        height: 300.0, child: new Center(child:
      new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          new CircularProgressIndicator(
            strokeWidth: 1.0,),
          new Text("正在載入"),
        ],)),)
    ];
  }
複製程式碼

然後我們再來看看效果圖

image

gay gay gayhub

下次再見咯!

相關文章