博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从0开始写一个基于Flutter的开源中国客户端(7)——App网络请求和数据存储
阅读量:5857 次
发布时间:2019-06-19

本文共 11208 字,大约阅读时间需要 37 分钟。

中我记录了基于Flutter的开源中国客户端各个静态页面的实现,主要是UI的实现,没有涉及到任何网络请求,数据加载、存储等方面。本篇记录的是该项目中的网络请求和数据存储、加载的方式,希望自己在温故知新的同时能给Flutter初学者带来帮助。

索引 文章
1
2
3
4
5
6
?7
8

Flutter中的网络请求

Flutter中已内置了网络请求库,可直接导入使用:

import 'package:http/http.dart' as http;复制代码

一个最简单的get请求代码如下:

import 'package:http/http.dart' as http;main() async {  http.Response res = await http.get("https://cn.bing.com");  print(res.body); // 打印出get请求返回的字符串数据}复制代码

控制台中会打印出请求返回的字符串数据。

另外也有一些开源的网络请求库,由于笔者暂时没有用过,所以在本篇中不详细说了。

在基于Flutter的开源中国客户端中,使用的也是Flutter内置的网络请求库,但是做了一些简单的封装,主要代码在lib/util/NetUtils.dart文件中,代码如下:

import 'dart:async';import 'package:http/http.dart' as http;class NetUtils {  // get请求的封装,传入的两个参数分别是请求URL和请求参数,请求参数以map的形式传入,会在方法体中自动拼接到URL后面  static Future
get(String url, {Map
params}) async { if (params != null && params.isNotEmpty) { // 如果参数不为空,则将参数拼接到URL后面 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; } http.Response res = await http.get(url); return res.body; } // post请求 static Future
post(String url, {Map
params}) async { http.Response res = await http.post(url, body: params); return res.body; }}复制代码

使用该工具类的方法也很简单,如下代码所示:

import 'util/NetUtils.dart';main() {  Map
map = new Map(); map['name'] = 'zhangsan'; map['age'] = '20'; NetUtils.get("http://www.baidu.com", params: map).then((res) { print(res); });}复制代码

Flutter中的数据存储

一般移动应用开发中的数据存储基本上都是文件、数据库等方式。Flutter没有提供直接操作数据库的API,但是有第三方的插件可以用,比如sqflite,关于这个插件的使用方法,可以查看,由于在基于Flutter的开源中国客户端项目中没有用到数据库,所以这几也不做详细说明了。

本项目中针对token,用户信息的存储,使用的是Flutter提供的类似于Android的SharedPreferences,这个库是以插件的形式提供的,并没有内置到Flutter中,所以我们需要为项目配置插件,在pubspec.yaml文件中,加入如下配置:

dependencies:  flutter:    sdk: flutter      shared_preferences: "^0.4.1"复制代码

然后执行flutter packages get命令即可自动安装插件,如果你使用AndroidStudio作为开发工具,当pubspec.yaml文件做了修改后,页面上方会自动出现提示,点击Packages get即可。

插件安装成功后,使用起来很容易,如下代码所示:

import 'package:shared_preferences/shared_preferences.dart';main() async {  SharedPreferences sp = await SharedPreferences.getInstance();  sp.setString("name", "zhangsan");  sp.setInt("age", 20);  sp.setBool("isLogin", false);  sp.setDouble("price", 100.5);}复制代码

要获取存储的某个数据,只需要使用sp.get(key)即可。shared_preferences插件的主页在。

关于插件的使用方法,这里说明一下:是Flutter提供的一个插件仓库,可以发布有关dart或flutter的插件。如果我们需要实现某个功能,而flutter又没有提供类似的功能时,可以上这个网站上搜索相关关键字,也许就有人已经发布了他写的库,正好可以实现我们需要的功能。

上面简要说明了Flutter中的网络请求和数据存储,下面结合项目来说明如何加载网络数据,如何保存用户信息等数据。

从网络加载资讯列表并显示

上一篇中我记录了如何显示资讯列表,但是完全是一个静态的资讯列表,里面的数据都是测试的假数据,这一篇就记录下如何从接口获取真实的资讯数据并显示出来。

在基于Flutter的开源中国客户端项目中,由于开源中国官方的openapi提供的数据比较少,故资讯列表没有使用开源中国官方提供的接口,是笔者用python抓的网站数据,接口部署在香港的云服务器上,若有访问较慢的情况,请谅解。另外,接口没有做任何认证,请不要频繁请求接口。

显示加载中的Loading

既然是从网络上加载数据,那必然会有一个耗时的等待期,需要给加载过程展示一个Loading,这里我们为NewsListPage添加一个listData变量,如果该变量为null,则显示Loading,否则就显示列表数据,显示Loading的同时从网络上请求数据,一旦有数据后,就通过setState更新listData,主要代码如下(NewsListPage.dart文件):

@override  Widget build(BuildContext context) {    // 无数据时,显示Loading    if (listData == null) {      return new Center(        // CircularProgressIndicator是一个圆形的Loading进度条        child: new CircularProgressIndicator(),      );    } else {      // 有数据,显示ListView      Widget listView = new ListView.builder(        itemCount: listData.length * 2,        itemBuilder: (context, i) => renderRow(i),        controller: _controller,      );      // RefreshIndicator为ListView增加了下拉刷新能力,onRefresh参数传入一个方法,在下拉刷新时调用      return new RefreshIndicator(child: listView, onRefresh: _pullToRefresh);    }  }    @override  void initState() {    super.initState();    getNewsList(false);  }    // 从网络获取数据,isLoadMore表示是否是加载更多数据  getNewsList(bool isLoadMore) {    String url = Api.NEWS_LIST;    // curPage是定义在NewsListPageState中的成员变量,表示当前加载的页面索引    url += "?pageIndex=$curPage&pageSize=10";    NetUtils.get(url).then((data) {      if (data != null) {        // 将接口返回的json字符串解析为map类型,需要导入包:import 'dart:convert';        Map
map = json.decode(data); if (map['code'] == 0) { // code=0表示请求成功 var msg = map['msg']; // total表示资讯总条数 listTotalSize = msg['news']['total']; // data为数据内容,其中包含slide和news两部分,分别表示头部轮播图数据,和下面的列表数据 var _listData = msg['news']['data']; var _slideData = msg['slide']; setState(() { if (!isLoadMore) { // 不是加载更多,则直接为变量赋值 listData = _listData; slideData = _slideData; } else { // 是加载更多,则需要将取到的news数据追加到原来的数据后面 List list1 = new List(); // 添加原来的数据 list1.addAll(listData); // 添加新取到的数据 list1.addAll(_listData); // 判断是否获取了所有的数据,如果是,则需要显示底部的"我也是有底线的"布局 if (list1.length >= listTotalSize) { list1.add(Constants.END_LINE_TAG); } // 给列表数据赋值 listData = list1; // 轮播图数据 slideData = _slideData; } }); } } }); }复制代码

上面的代码中是处理显示Loading和显示数据列表的不同逻辑,然后还有加载更多的逻辑处理,但是什么时候去加载更多数据呢?很显然,应该监听列表的滚动,当列表滚动到底时,主动去加载下一页数据。

加载下一页数据

在上面的代码中,我们在创建ListView时,传入了一个controller参数,这个controller就是为了监听列表滚动事件而传入的,它是一个ScrollController对象,我们在NewsListPageState类中定义这个变量并初始化:

ScrollController _controller = new ScrollController();复制代码

要监听列表是否滚动到底的事件,还需要给这个controller添加Listener,在NewsListPageState类的构造方法中添加如下代码:

NewsListPageState() {    _controller.addListener(() {      // 表示列表的最大滚动距离       var maxScroll = _controller.position.maxScrollExtent;      // 表示当前列表已向下滚动的距离      var pixels = _controller.position.pixels;      // 如果两个值相等,表示滚动到底,并且如果列表没有加载完所有数据      if (maxScroll == pixels && listData.length < listTotalSize) {        // scroll to bottom, get next page data        curPage++; // 当前页索引加1        getNewsList(true); // 获取下一页数据      }    });  }复制代码

给ListView加入下拉刷新能力

其实在上面的代码中已经为ListView添加了下拉刷新的能力,就是build方法返回时,为ListView包裹了一层RefreshIndicator:

return new RefreshIndicator(child: listView, onRefresh: _pullToRefresh);复制代码

_pullToRefresh方法会在下拉刷新的时候调用,因为是下拉刷新,所以取的是第一页数据,并且不是加载更多,所以方法体如下:

Future
_pullToRefresh() async { curPage = 1; getNewsList(false); return null; }复制代码

需要注意的是,onRefresh参数需要一个Future<Null>类型的数据,所以上面的_pullToRefresh才会返回Future<Null>

改造过后的资讯列表如下gif图所示(图比较大,加载会有点慢):

保存登录后的用户数据

由于获取动弹信息,评论动弹等,都需要调用开源中国的openapi,而这些接口都是需要AccessToken和用户id的,所以我们必须把用户登录后的数据保存下来,以便在需要用到这些数据时能获取到。具体的如何实现登录将会放在下一篇——Flutter插件的使用中说明。本篇暂时忽略登录的过程,只说明登录后如何保存用户信息。

为了统一管理SharedPreferences,这里我们新建一个工具类DataUtils,文件目录在lib/util/DataUtils.dart。开源中国openapi调用接口成功登录后,会返回以下信息:

字段名 字段类型 说明
access_token String access_token值
refresh_token String refresh_token值
uid int 授权用户的uid
tokenType String access_token类型
expires_in int 超时时间(单位秒)

为了在SharedPreferences中保存以上信息,先在DataUtils中声明每个字段对应的key,代码如下:

static final String SP_AC_TOKEN = "accessToken";  static final String SP_RE_TOKEN = "refreshToken";  static final String SP_UID = "uid";  static final String SP_IS_LOGIN = "isLogin"; // SP_IS_LOGIN标记是否登录  static final String SP_EXPIRES_IN = "expiresIn";  static final String SP_TOKEN_TYPE = "tokenType";复制代码

然后提供一个静态方法用于一次性保存这些信息:

// 保存用户登录信息,data中包含了token等信息  static saveLoginInfo(Map data) async {    if (data != null) {      SharedPreferences sp = await SharedPreferences.getInstance();      String accessToken = data['access_token'];      await sp.setString(SP_AC_TOKEN, accessToken);      String refreshToken = data['refresh_token'];      await sp.setString(SP_RE_TOKEN, refreshToken);      num uid = data['uid'];      await sp.setInt(SP_UID, uid);      String tokenType = data['tokenType'];      await sp.setString(SP_TOKEN_TYPE, tokenType);      num expiresIn = data['expires_in'];      await sp.setInt(SP_EXPIRES_IN, expiresIn);      await sp.setBool(SP_IS_LOGIN, true); // SP_IS_LOGIN标记是否登录    }  }复制代码

登录成功后就可以调用开源中国的openapi获取用户信息了,跟上面类似,先定义用户信息每个字段对应的key:

static final String SP_USER_NAME = "name";  static final String SP_USER_ID = "id";  static final String SP_USER_LOC = "location";  static final String SP_USER_GENDER = "gender";  static final String SP_USER_AVATAR = "avatar";  static final String SP_USER_EMAIL = "email";  static final String SP_USER_URL = "url";复制代码

根据命名就知道每个字段代表的什么含义,这里就不细说了,然后还是提供一个静态方法,用于一次性保存用户信息:

// 保存用户个人信息  static Future
saveUserInfo(Map data) async { if (data != null) { SharedPreferences sp = await SharedPreferences.getInstance(); String name = data['name']; num id = data['id']; String gender = data['gender']; String location = data['location']; String avatar = data['avatar']; String email = data['email']; String url = data['url']; await sp.setString(SP_USER_NAME, name); await sp.setInt(SP_USER_ID, id); await sp.setString(SP_USER_GENDER, gender); await sp.setString(SP_USER_AVATAR, avatar); await sp.setString(SP_USER_LOC, location); await sp.setString(SP_USER_EMAIL, email); await sp.setString(SP_USER_URL, url); UserInfo userInfo = new UserInfo( id: id, name: name, gender: gender, avatar: avatar, email: email, location: location, url: url ); return userInfo; } return null; }复制代码

保存用户信息是一个异步的过程,其中UserInfo是定义在lib/model/下的一个实体类,代码如下:

// 用户信息class UserInfo {  String gender;  String name;  String location;  num id;  String avatar;  String email;  String url;  UserInfo({this.id, this.name, this.gender, this.avatar, this.email, this.location, this.url});}复制代码

为了方便的拿到保存的用户信息和AccessToken数据,以及判断当前是否登录,为DataUtils提供三个静态方法:

// 获取用户信息  static Future
getUserInfo() async { SharedPreferences sp = await SharedPreferences.getInstance(); bool isLogin = sp.getBool(SP_IS_LOGIN); if (isLogin == null || !isLogin) { return null; } UserInfo userInfo = new UserInfo(); userInfo.id = sp.getInt(SP_USER_ID); userInfo.name = sp.getString(SP_USER_NAME); userInfo.avatar = sp.getString(SP_USER_AVATAR); userInfo.email = sp.getString(SP_USER_EMAIL); userInfo.location = sp.getString(SP_USER_LOC); userInfo.gender = sp.getString(SP_USER_GENDER); userInfo.url = sp.getString(SP_USER_URL); return userInfo; } // 是否登录 static Future
isLogin() async { SharedPreferences sp = await SharedPreferences.getInstance(); bool b = sp.getBool(SP_IS_LOGIN); return b != null && b; } // 获取accesstoken static Future
getAccessToken() async { SharedPreferences sp = await SharedPreferences.getInstance(); return sp.getString(SP_AC_TOKEN); }复制代码

如果用户注销登录,需要清除已保存的用户信息:

// 清除登录信息  static clearLoginInfo() async {    SharedPreferences sp = await SharedPreferences.getInstance();    await sp.setString(SP_AC_TOKEN, "");    await sp.setString(SP_RE_TOKEN, "");    await sp.setInt(SP_UID, -1);    await sp.setString(SP_TOKEN_TYPE, "");    await sp.setInt(SP_EXPIRES_IN, -1);    await sp.setBool(SP_IS_LOGIN, false);  }复制代码

源码

本篇相关的所有源码都在GitHub上demo-flutter-osc项目的。

后记

本篇主要记录的是基于Flutter的开源中国客户端app中的网络请求和数据存储方式,写得不清楚的地方请多包涵,有问题可以留言告诉笔者。下一篇将记录Flutter中的插件使用。

我的开源项目

  1. 基于Google Flutter的开源中国客户端,希望大家给个Star支持一下,源码:
  1. 基于Flutter的俄罗斯方块小游戏,希望大家给个Star支持一下,源码:
上一篇 下一篇

转载地址:http://skljx.baihongyu.com/

你可能感兴趣的文章
Akka actor tell, ask 函数的实现
查看>>
windows10 chrome 调试 ios safari 方法
查看>>
Hello , Ruby!
查看>>
Netty 4.1.35.Final 发布,经典开源 Java 网络服务框架
查看>>
详解Microsoft.AspNetCore.CookiePolicy
查看>>
go与c互相调用
查看>>
如何优雅地用Redis实现分布式锁
查看>>
从零开始Docker化你的Node.js应用
查看>>
SCDPM2012 R2实战一:基于SQL 2008 R2集群的SCDPM2012 R2的安装
查看>>
SQL SERVER中字段类型与C#数据类型的对应关系
查看>>
保存对象、关系映射
查看>>
Java堆和栈的区别
查看>>
Linux lsof命令详解
查看>>
SVG path
查看>>
js判断checkbox是否选中
查看>>
【转】TabError:inconsistent use of tabs and spaces
查看>>
链路层
查看>>
多系统盘挂载
查看>>
MySQL函数怎么加锁_MYSQL 函数调用导致自动生成共享锁问题
查看>>
java map 写法_Java中List与Map初始化的一些写法
查看>>