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

索引 文章
1 从0开始写一个基于Flutter的开源中国客户端(1)
Flutter简介及开发环境搭建 | 掘金技术征文
2 从0开始写一个基于Flutter的开源中国客户端(2)
Dart语法基础
3 从0开始写一个基于Flutter的开源中国客户端(3)
初识Flutter & 常用的Widgets
4 从0开始写一个基于Flutter的开源中国客户端(4)
Flutter布局基础
5 从0开始写一个基于Flutter的开源中国客户端(5)
App整体布局框架搭建
6 从0开始写一个基于Flutter的开源中国客户端(6)
各个静态页面的实现
?7 从0开始写一个基于Flutter的开源中国客户端(7)
App网络请求和数据存储
8 从0开始写一个基于Flutter的开源中国客户端(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<String> get(String url, {Map<String, String> 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<String> post(String url, {Map<String, String> params}) async {http.Response res = await http.post(url, body: params);return res.body;}
}
复制代码

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

import 'util/NetUtils.dart';main() {Map<String, String> 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: fluttershared_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插件的主页在这里。

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

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

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

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

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

显示加载中的Loading

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

  @overrideWidget build(BuildContext context) {// 无数据时,显示Loadingif (listData == null) {return new Center(// CircularProgressIndicator是一个圆形的Loading进度条child: new CircularProgressIndicator(),);} else {// 有数据,显示ListViewWidget 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);}}@overridevoid 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<String, dynamic> 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 datacurPage++; // 当前页索引加1getNewsList(true); // 获取下一页数据}});}
复制代码

给ListView加入下拉刷新能力

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

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

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

  Future<Null> _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<UserInfo> 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<UserInfo> 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<bool> isLogin() async {SharedPreferences sp = await SharedPreferences.getInstance();bool b = sp.getBool(SP_IS_LOGIN);return b != null && b;}// 获取accesstokenstatic Future<String> 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项目的v0.3分支。

后记

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

我的开源项目

  1. 基于Google Flutter的开源中国客户端,希望大家给个Star支持一下,源码:
  • GitHub
  • 码云
  1. 基于Flutter的俄罗斯方块小游戏,希望大家给个Star支持一下,源码:
  • GitHub
  • 码云
上一篇 下一篇
从0开始写一个基于Flutter的开源中国客户端(6)
——各个静态页面的实现
从0开始写一个基于Flutter的开源中国客户端(8)
——插件的使用

从0开始写一个基于Flutter的开源中国客户端(7)——App网络请求和数据存储相关推荐

  1. 从0开始写一个基于Flutter的开源中国客户端(5)——App整体布局框架搭建

    上一篇中我记录了Flutter中常用的一些布局,本篇开始开发基于Flutter的开源中国客户端了.在本篇博客中,要实现的是一个App的整体框架,包括页面底部的Tab导航菜单.页面的侧滑菜单以及跳转到新 ...

  2. 手写一个基于NIO的迷你版Tomcat

    笔者也建立的自己的公众号啦,平时会分享一些编程知识,欢迎各位大佬支持~ 扫码或微信搜索北风IT之路关注 本文公众号地址:手写一个基于NIO的迷你版Tomcat 在很久之前看到了一篇文章写一个迷你版的T ...

  3. [从 0 开始写一个操作系统] 一、准备知识

    从 0 开始写一个操作系统 作者:解琛 时间:2020 年 8 月 29 日 从 0 开始写一个操作系统 一.准备知识 1.1 实现方案 1.2 gcc 1.2.1 AT&T 汇编基本语法 1 ...

  4. 给微软的日志框架写一个基于委托的日志提供者

    动手造轮子:给微软的日志框架写一个基于委托的日志提供者 Intro 微软的日志框架现在已经比较通用,有时候我们不想使用外部的日志提供者,但又希望提供一个比较简单的委托就可以实现日志记录,于是就有了后面 ...

  5. java编写一个框架_手把手教你写一个基于 RxJava 的扩展框架

    背景 现在 RxJava 在 Android 开发中可谓时炽手可热,其受欢迎程度不言而喻,也因此在 github 上出现了一系列的基于 RxJava 的框架,如 RxBinding.RxPermiss ...

  6. 从0开始写一个小程序

    项目简介 从0开始写一个小程序,本来想写一个新闻类的程序,后来发现调用的聚合数据api每天只能访问100次,就换成豆瓣的了,直接用豆瓣的接口又访问不了,在网上查了一下,要把豆瓣的地址换成"h ...

  7. 从0开始写一个多线程爬虫(2)

    上一篇文章: 从0开始写一个多线程爬虫(1) 我们用继承Thread类的方式来改造多线程爬虫,其实主要就是把上一篇文章的代码写到线程类的run方法中,代码如下: import re import re ...

  8. 详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

      本文主要内容:详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,帮助我们调节一些参数,比如电机PID的调节.波形融合等,以及在我们写通信协议的时候 ...

  9. 从0开始写一个播放器系列-开篇

    从0开始写一个播放器系列-开篇 阅读本系列博客所需要具备的知识: js, ts , canvas Api , DOM, webpack, 不懂也没有太大的关系, 我会捎带着写出来, 剩下的自行百度 第 ...

  10. 一款基于flutter的仿微博客户端(仿微博首页,视频页,聊天页面等)

    基于flutter的仿微博客户端 在学习了flutter之后,写了一个仿微博最新的10.4.0版本, 还原微博80%的界面 总共涉及到了几十个界面和接口,用到了flutter中的大部分组件 该项目分为 ...

最新文章

  1. Linux Shell 脚本限制ssh最大用户登录数
  2. 三杯茶(一本令全世界为之动容的书)(Three cups of tea)
  3. EXEC与sp_executesql的区别及应用
  4. dbms_排名前50位的DBMS面试问答
  5. 未来的语音世界——中国智能语音市场分析
  6. 正则表达式 RegularExpressions
  7. 自动化运维工具Ansible实战(四)常用模块
  8. iPhone 12顶配版延期到10月:刘海仍在 后置3摄+雷达
  9. 使用LoadBalancerClient就行服务消费
  10. C#入门学习——超市收银系统
  11. 华为机试题输入输出总结
  12. Oracle 数据库(一)—— Oracle 数据库基本介绍
  13. Win11C盘扩容详细教程
  14. 怎样提升自己的编程能力?
  15. Linux的内核态与用户态
  16. 很多人知道外包的种种不好,但还是选择去外包,这是为什么呢?
  17. PDF合并在电脑上怎样实现?PDF合并的方法有哪些?
  18. 【CC2640】CC2640架构及原理
  19. 比特率 Kbps kbit/s 1KB/s Byte 的正确理解
  20. 【BOOT】多重引导程序启动U盘

热门文章

  1. Atitit 管理plus 的概念,为什么要留长发与管理思想的结合 目录 1.1. 孝道的体现 身体发肤 受之发肤 不敢毁伤 出自 1 1.2. 著作介绍 1 1.3. 传统国学文化的复兴 中国
  2. Atitti  css   transition Animation differ区别
  3. Atitit 编程语言原理与概论attilax总结
  4. paip.输入法编程----一级汉字1000个
  5. 公募基金行业与146家公募基金管理机构手册
  6. julia的几种画图方法
  7. 重磅!阿里云发布业界首本云网络白皮书
  8. 服务器cpu最多几核心,决定虚拟服务器所需要的CPU核心数量是一件非常复杂的事情...
  9. 【病灶分类】基于matlab粒子群算法优化SVM病灶分类【含Matlab源码 1520期】
  10. 【单目标优化求解】基于matlab遗传算法求解单目标优化问题【含Matlab源码 1320期】