在本文中,我们将带你了解Flutter进阶—网络和HTTP在这篇文章中,我们将为您详细介绍Flutter进阶—网络和HTTP的方方面面,并解答flutter网络状态常见的疑惑,同时我们还将给您一些技巧
在本文中,我们将带你了解Flutter进阶—网络和HTTP在这篇文章中,我们将为您详细介绍Flutter进阶—网络和HTTP的方方面面,并解答flutter 网络状态常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的Flutter -------- Http 库 网络请求封装(HttpController)、Flutter之测试Http和HttpClient、Flutter进阶—Firebase数据库实例、Flutter进阶—使用自定义字体。
本文目录一览:- Flutter进阶—网络和HTTP(flutter 网络状态)
- Flutter -------- Http 库 网络请求封装(HttpController)
- Flutter之测试Http和HttpClient
- Flutter进阶—Firebase数据库实例
- Flutter进阶—使用自定义字体
Flutter进阶—网络和HTTP(flutter 网络状态)
使用Http包
Flutter支持Http包,版本0.11.3+12或更高版本,首先在pubspec.yaml
中声明对http的依赖,注意添加声明后按顶部的“Packages get”:
dependencies: Flutter: sdk: Flutter http: '>=0.11.3+12'
发出HTTP请求
接下来,创建一个HTTP客户端(Client),我们建议使用createHttpClient来启用测试以提供http.MockClient
:
import 'package:Flutter/services.dart'; var httpClient = createHttpClient();
客户端支持常见的HTTP操作,比如:
HTTP GET:使用
get
获取一般的请求,read
返回字符串的请求或返回字节的请求的readbytes
。HTTP POST:使用
post
作为一般的的post。
演示代码:
postData() async { ... var response = await httpClient.post(url,body: {'name': 'doodle','color': 'blue'}); print('Response status: ${response.statusCode}'); }
需要注意的是,HTTP API在返回值中使用Dart Futures,我们建议您使用具有async/await
语法的API调用,比如上面的演示代码。
解码和编码JSON
支持解码和编码JSON的功能由dart:convert
库提供,解码JSON字符串并将响应解析为Map:
Map data = JSON.decode(response.body); /* 假设响应内容是这样的:['foo',{ 'bar': 499 }] barValue设置为499 */ int barValue = data[1]['bar'];
要对JSON进行编码,要将一个简单的值(字符串,布尔值或数字文字)或Map、List或包含简单值的Map列表传递给encode
方法:
String encodedString = JSON.encode([1,2,{ 'a': null }]);
演示实例
这个实例演示了如何在Flutter应用程序中从HTTPS GET调用中解码JSON,它调用httpbin.com
的Web服务测试API,然后响应您的本地IP地址。请注意,使用安全网络(HTTPS)。
首先添加http依赖关系,然后再将lib/main.dart的内容替换为以下内容:
import 'dart:convert'; import 'package:Flutter/material.dart'; import 'package:Flutter/services.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo',home: new MyHomePage(),); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key}) : super(key: key); @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { String _ipAddress = "未知"; _getIPAddress() async { String url = 'https://httpbin.org/ip'; var httpClient = createHttpClient(); var response = await httpClient.read(url); Map data = JSON.decode(response); String ip = data['origin']; /* bool mounted 这个状态对象当前是否在树中。 用于此处,如果控件在数据正在请求时从树中删除,则我们要丢弃该数据,而不是调用setState来更新实际不存在的内容。 */ if(!mounted) return; setState((){ _ipAddress = ip; }); } @override Widget build(BuildContext context) { var spacer = new SizedBox(height: 32.0); return new Scaffold( body: new Center( child: new Column( children: <Widget> [ spacer,new Text('您当前的IP地址是:'),new Text('$_ipAddress'),spacer,new RaisedButton( onpressed: _getIPAddress,child: new Text('获取IP地址'),) ] ) ) ); } }
Flutter -------- Http 库 网络请求封装(HttpController)
http 库 再次封装的网络请求类 HttpController
1. 添加依赖
dependencies:
http: ^0.12.0 #latest version
2. 导入库
import ''package:http/http.dart'' as http; //导入前需要配置
效果图:
封装类
import ''package:http/http.dart'' as http;
class HttpController {
static void get(String url, Function callback,
{Map<String, String> params, Function errorCallback}) async {
if (params != null && params.isNotEmpty) {
StringBuffer sb = new StringBuffer("?");
params.forEach((key, value) {
sb.write("$key" + "=" + "$value" + "&");
});
String paramStr = sb.toString();
paramStr = paramStr.substring(0, paramStr.length - 1);
url += paramStr;
}
try {
http.Response res = await http.get(url);
if (callback != null) {
callback(res.body);
}
} catch (exception) {
if (errorCallback != null) {
errorCallback(exception);
}
}
}
static void post(String url, Function callback,
{Map<String, String> params, Function errorCallback}) async {
try {
http.Response res = await http.post(url, body: params);
if (callback != null) {
callback(res.body);
}
} catch (e) {
if (errorCallback != null) {
errorCallback(e);
}
}
}
}
调用:
import ''dart:convert'';
import ''package:flutter/material.dart'';
import ''package:flutter_test1/http/HttpController.dart'';
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new Page();
}
}
class Page extends State<HomePage> {
String dataStr = "";
var _items = [];
@override
Widget build(BuildContext context) {
return layout(context);
}
@override
void initState() {
super.initState();
getData();
}
void getData() {
Map<String, String> map = new Map();
map["v"] = "1.0";
map["month"] = "7";
map["day"] = "25";
map["key"] = "bd6e35a2691ae5bb8425c8631e475c2a";
HttpController.post("http://api.juheapi.com/japi/toh", (data) {
if (data != null) {
final body = json.decode(data.toString());
final feeds = body["result"];
var items = [];
feeds.forEach((item) {
items.add(Model(item["_id"], item["title"], item["pic"], item["year"],
item["month"], item["day"], item["des"], item["lunar"]));
});
setState(() {
dataStr = data.toString();
_items = items;
});
}
}, params: map);
}
Widget layout(BuildContext context) {
return new Scaffold(
appBar: buildAppBar(context),
body:
new ListView.builder(itemCount: _items.length, itemBuilder: itemView),
);
}
Widget itemView(BuildContext context, int index) {
Model model = this._items[index];
//设置分割线
if (index.isOdd) return new Divider(height: 2.0);
return new Container(
color: Color.fromARGB(0x22, 0x49, 0xa9, 0x8d),
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Text(''${model.year}年${model.month}月${model.day}日'',
style: new TextStyle(fontSize: 15.0)),
new Text(''(${model.lunar})'',
style: new TextStyle(fontSize: 15.0)),
],
),
new Center(
heightFactor: 6.0,
child: new Text("${model.title}",
style: new TextStyle(fontSize: 17.0)),
)
],
))));
}
Widget buildAppBar(BuildContext context) {
return new AppBar(title: const Text(''历史今日''));
}
}
class Model {
String _id;
String title;
String pic;
int year;
int month;
int day;
String des;
String lunar;
Model(this._id, this.title, this.pic, this.year, this.month, this.day,
this.des, this.lunar);
}
Flutter之测试Http和HttpClient
1 测试Http和HttpClient
导入包:在pubspec.yaml里面导入
http: ^0.12.2
main.dart里面导入
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
2 代码实现
import 'package:Flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io';
void main() {
runApp(MyApp1());
}
class MyApp1 extends StatelessWidget {
void getWeatherData() async {
try {
HttpClient httpClient = HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse("http://pv.sohu.com/cityjson?ie=utf-8"));
HttpClientResponse response = await request.close();
var result = await response.transform(utf8.decoder).join();
print(result);
httpClient.close();
} catch (e) {
print("get data fail $e");
} finally {
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'open url',home: Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method,and use it to set our appbar title.
title: Text('hello Flutter'),),body: Center(
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default,it sizes itself to fit its
// children horizontally,and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console,choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio,or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has varIoUs properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[
Text(
'hello word Flutter',RaisedButton(
onpressed: () {
const url = 'https://www.baidu.com';
http.get(url).then((response) {
print("状态 is ${response.statusCode}");
print("内容 is ${response.body}");
}
);
},child: Text('test Http'),RaisedButton(
onpressed: getWeatherData,child: Text('test HttpClient get weather'),],);
}
}
3 运行结果
点击test Http日志打印如下
I/Flutter (27404): 状态 is 200
I/Flutter (27404): 内容 is <html>
I/Flutter (27404): <head>
I/Flutter (27404): <script>
I/Flutter (27404): location.replace(location.href.replace("https://","http://"));
I/Flutter (27404): </script>
I/Flutter (27404): </head>
I/Flutter (27404): <body>
I/Flutter (27404): <noscript><Meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
I/Flutter (27404): </body>
I/Flutter (27404): </html>
点击 test HttpClient get weather日志打印如下
I/Flutter (27404): var returnCitySN = {"cip": "220.250.29.154","cid": "350100","cname": "福建省福州市"};
Flutter进阶—Firebase数据库实例
如果需要使用Firebase实时数据库。首先我们需要为项目配置Firebase,具体配置方法可以在《Flutter实战一Flutter聊天应用(五)》查看,因为我们只需要使用Firebase数据库,所以pubspec.yaml
文件的内容需要修改一下。
name: talk_casually description: A new Flutter project. dependencies: Flutter: sdk: Flutter firebase_database: ^0.0.4
首先我们修改项目的main.dart
文件,代码如下。
import 'dart:async'; import 'package:Flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/ui/firebase_animated_list.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter数据库实例',home: new MyHomePage(),); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: const Text('Flutter数据库实例'),),); } }
打开Firebase控制台,更改Firebase实时数据库的安全规则,选择“Database > 规则”,规则如下所示。
{ "rules": { "counter":{ ".read": true,".write": true },"messages":{ ".read": true,".write": true } } }
现在我们需要使用DatabaseReference
连接到Firebase数据库,DatabaseReference
表示Firebase数据库中的特定位置,可用于读取或写入数据到该位置。
class _MyHomePageState extends State<MyHomePage> { //... final DatabaseReference _counterRef = FirebaseDatabase.instance.reference().child('counter'); final DatabaseReference _messagesRef = FirebaseDatabase.instance.reference().child('messages'); //... }
FirebaseDatabase
是访问Firebase数据库的入口点,我们可以通过调用FirebaseDatabase.instance
获取一个实例,要访问数据库中的位置并读取或写入数据,需要使用reference
。child
获取指定相对路径中的位置的DatabaseReference
,相对路径可以是简单的子key(例如“fred”)或更深的斜线分隔的路径(例如“fred/name/first”)。
我们先增加一个_counter
存储按钮点击次数。在默认情况下,用户的写入操作和缓存数据仅存储在内存中,并在用户重新启动应用程序时丢失。通过将启用持久性设置为true
,数据将持续存储在设备(磁盘)存储上,并在应用程序重新启动时再次可用(即使当时没有网络连接)。setPersistenceEnabled
能够开启数据库的持久性设置,必须在调用数据库引用方法之前设置此属性,并且每个应用程序只需要调用一次。如果操作成功,则Future将返回true
,如果持久性无法设置(可能是已经创建数据库引用),则返回false
。
class _MyHomePageState extends State<MyHomePage> { //... int _counter; //... @override void initState() { super.initState(); FirebaseDatabase.instance.setPersistenceEnabled(true); } //... }
现在我们的应用程序中,Firebase数据库客户端将缓存同步的数据,并跟踪用户在应用程序运行时发起的所有写入。当网络连接恢复时,它可以无缝地处理间歇性网络连接并重新发送写入操作。默认情况下,Firebase数据库客户端将使用高达10MB的磁盘空间来缓存数据。如果缓存增长超过此大小,客户端将开始删除尚未使用的数据。如果发现应用程序缓存数据太少或太多,可以使用setPersistenceCacheSizeBytes
来更改缓存大小。此属性必须在调用数据库引用方法之前设置,并且每个应用程序只需要调用一次。
class _MyHomePageState extends State<MyHomePage> { //... @override void initState() { super.initState(); FirebaseDatabase.instance.setPersistenceEnabled(true); FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000); } //... }
如果操作成功,则Future将返回true
,如果该值无法设置,则为false
(可能已经创建数据库引用)。需要注意的是,指定的高速缓存大小只是一个近似值,并且磁盘上的大小有时会暂时超过它。该属性不支持小于1MB或大于100MB的缓存大小。
class _MyHomePageState extends State<MyHomePage> { //... @override void initState() { super.initState(); FirebaseDatabase.instance.setPersistenceEnabled(true); FirebaseDatabase.instance.setPersistenceCacheSizeBytes(10000000); _counterRef.keepSynced(true); } //... }
通过在某个位置调用keepSynced(true)
,即使没有为该位置附加任何监听器,该位置的数据将自动下载并保持同步。另外,当一个位置保持同步时,它不会被从持久磁盘缓存中逐出。
现在我们增加两个StreamSubscription
类型的变量,StreamSubscription
表示来自Stream
的事件的订阅。订阅将提供事件给侦听器,并保存用于处理事件的回调。订阅也可以用于取消订阅事件,或者临时暂停Stream
中的事件。Stream
是异步数据事件的来源,Stream
提供了一种接收一系列事件的方式。每个Event
都是一个数据事件,也称为流的元素,或者一个错误事件。当流已经发出其所有事件时,一个“完成”事件将通知侦听器已到达结束。我们可以监听流,使其开始生成事件,并设置接收事件的侦听器。当我们侦听时,将收到一个StreamSubscription
对象,该对象是提供事件的活动对象,可以用于停止再次监听,或临时暂停订阅的事件。
class _MyHomePageState extends State<MyHomePage> { //... StreamSubscription<Event> _counterSubscription; StreamSubscription<Event> _messagesSubscription; //... }
现在需要在应用程序启动时读取按钮被点击的次数。onValue
表示当该位置的数据更新时触发。Event
里封装了DataSnapshot
,也可能是其以前的兄弟的key,通常用于命令快照(即返回一个DataSnapshot
)。DataSnapshot
包含来自Firebase数据库位置的数据,每当我们读取Firebase数据时,将收到一个DataSnapshot
。DataSnapshot
的value
属性以本机类型返回此数据快照的内容。
class _MyHomePageState extends State<MyHomePage> { //... @override void initState() { //... _counterSubscription = _counterRef.onValue.listen((Event event) { setState(() { _counter = event.snapshot.value ?? 0; }); }); } //... }
上面代码中的双问号操作符意思是取所赋值??左边的,如果左边为null,取所赋值??右边的。应用程序在启动时还应该获取消息列表,还可以打印在控制台上。DatabaseReference
的limitToLast
方法会创建一个有限制的查询并将其锚定到窗口的末尾。onChildAdded
表示在子数据加入时调用。当我们使用Stream.listen
在Stream
上侦听时,会返回一个StreamSubscription
对象。
class _MyHomePageState extends State<MyHomePage> { //... @override void initState() { //... _messagesSubscription = _messagesRef.limitToLast(10).onChildAdded.listen((Event event) { print('子数据增加了: ${event.snapshot.value}'); }); } //... }
在资源使用完之后及时关闭是一个良好的习惯,StreamSubscription
的cancel
方法用于取消订阅,在此调用之后,订阅不再接收事件。流可能需要关闭事件源,并在订阅取消后进行清理。
class _MyHomePageState extends State<MyHomePage> { //... @override void dispose() { super.dispose(); _messagesSubscription.cancel(); _counterSubscription.cancel(); } //... }
修改build
的内容,使我们可以看到查看按钮被点击了多少次。
class _MyHomePageState extends State<MyHomePage> { //... @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: const Text('Flutter数据库实例'),body: new Column( children: <Widget>[ new Flexible( child: new Center( child: new Text( '按钮点击 $_counter 次,\n\n该统计包括所有的终端!',],); } //... }
再增加一个_anchorToBottom
变量,控制显示消息列表的样式。
class _MyHomePageState extends State<MyHomePage> { //... bool _anchorToBottom = false; //... }
修改build
的内容,增加一个可选项,按钮如何显示消息列表。
class _MyHomePageState extends State<MyHomePage> { //... @override Widget build(BuildContext context) { return new Scaffold( //... body: new Column( children: <Widget>[ //... new ListTile( leading: new CheckBox( onChanged: (bool value) { setState(() { _anchorToBottom = value; }); },value: _anchorToBottom,title: const Text('锚点到底部'),); } //... }
现在可以使用FirebaseAnimatedList
控件把消息列表显示出来,FirebaseAnimatedList
是绑定到查询的AnimatedList
控件。
class _MyHomePageState extends State<MyHomePage> { //... @override Widget build(BuildContext context) { return new Scaffold( //... body: new Column( children: <Widget>[ //... new Flexible( child: new FirebaseAnimatedList( key: new ValueKey<bool>(_anchorToBottom),query: _messagesRef,reverse: _anchorToBottom,sort: _anchorToBottom ? (DataSnapshot a,DataSnapshot b) => b.key.compareto(a.key) : null,itemBuilder: (BuildContext context,DataSnapshot snapshot,Animation<double> animation) { return new SizeTransition( sizefactor: animation,child: new Text(snapshot.value.toString()),); },) ],); } //... }
在FirebaseAnimatedList
控件中,key
属性控制一个控件如何替换树中另一个控件,query
属性设置用于填充动画列表的Firebase查询,reverse
属性设置滚动视图是否在阅读方向滚动,sort
属性设置用于在排序列表时比较快照的可选功能,itemBuilder
属性根据需要调用来构建列表项控件。
我们需要编写一个提交事件,用于将数据上传到数据库。DatabaseReference
的once
方法侦听单个值事件,然后停止侦听。DatabaseReference
的set
方法将值写入具有指定优先级的位置(如果适用),这将覆盖此位置的所有数据。允许写入的数据类型有String
、boolean
、int
、double
、Map
和List
,写入的效果将立即可见,相应的事件将被触发。如果传递新值为null
,表示此位置的所有数据将被删除。
class _MyHomePageState extends State<MyHomePage> { //... String _kTestKey = 'Hello'; String _kTestValue = 'world!'; //... Future<Null> _increment() async { final DataSnapshot snapshot = await _counterRef.once(); setState(() { _counter = (snapshot.value ?? 0) + 1; }); _counterRef.set(_counter); _messagesRef.push().set(<String,String>{_kTestKey: '$_kTestValue $_counter'}); } //... }
DatabaseReference
的push
方法使用唯一key生成新的子位置并返回一个DatabaseReference
。当Firebase数据库位置的子位置是一个列表时,这个方法非常有用。由childByAutoId
生成的唯一key以客户端生成的时间戳为前缀,以便生成的列表将按时间顺序排序。
最后我们修改build
的内容,增加一个浮动按钮,并将上面编写的事件作为其点击事件。
class _MyHomePageState extends State<MyHomePage> { //... @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: const Text('Flutter数据库实例'),body: new Column( //... ),floatingActionButton: new FloatingActionButton( onpressed: _increment,tooltip: '增加',child: new Icon(Icons.add),); } //... }
启动应用程序,点击按钮三次,控制台与终端的显示如下:
数据库内容显示如下:
Flutter进阶—使用自定义字体
Android系统和IOS系统只有默认的几种字体,在大部分情况下是足够使用,但是在一些特殊的情况下,我们可能需要使用各种奇奇怪怪的自定义字体,下面就是这样一个例子。
import 'package:Flutter/material.dart'; class ImageDemo extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('使用自定义字体'),),body: new Center( child: new Column( children: <Widget> [ new Text( '\n关雎(先秦)\n' '关关雎鸠,在河之洲。\n' '窈窕淑女,君子好逑。\n' '参差荇菜,左右流之。\n' '窈窕淑女,寤寐求之。\n' '求之不得,寤寐思服。\n' '悠哉悠哉,辗转反侧。\n' '参差荇菜,左右采之。\n' '窈窕淑女,琴瑟友之。\n' '参差荇菜,左右芼之。\n' '窈窕淑女,钟鼓乐之。',style: new TextStyle( fontFamily: '暖色君萌妹体',fontSize: 24.0,) ] ) ),); } } void main() { runApp( new MaterialApp( title: 'Flutter教程',home: new ImageDemo(),); }
今天的关于Flutter进阶—网络和HTTP和flutter 网络状态的分享已经结束,谢谢您的关注,如果想了解更多关于Flutter -------- Http 库 网络请求封装(HttpController)、Flutter之测试Http和HttpClient、Flutter进阶—Firebase数据库实例、Flutter进阶—使用自定义字体的相关知识,请在本站进行查询。
本文标签: