框架
之前做 APP 開發(fā)的時候禾锤,我都是擔(dān)任 Android 組 leader,新項目起來,我會做技術(shù)預(yù)研滥搭,如《一套完整的 Android 通用框架》,一般會使用 MVP 模式(現(xiàn)在應(yīng)該是 MVVM 模式)捣鲸,網(wǎng)絡(luò)請求框架使用 Retrofit瑟匆,圖片加載使用 Glide,圖片縮放和裁剪分別使用 PhotoView 和 uCrop 等栽惶,必要時愁溜,我會寫個 sample 放項目里疾嗅,讓同事可以參考。
這個也是個新項目冕象,我也需要做下技術(shù)預(yù)研代承,F(xiàn)lutter 網(wǎng)絡(luò)請求框架需要使用什么?圖片加載又使用什么渐扮?文章詳情论悴,我打算使用 Markdown,這 Flutter 能實現(xiàn)嗎墓律?等等膀估,這些都是需要事前做好調(diào)研。
這個項目耻讽,代碼版本管理用 GitHub察纯,首先新建一個 Flutter 項目,GitHub 也新建個私有項目(暫時不公開吧)针肥,用如下命令將本地代碼和遠(yuǎn)程 GitHub 關(guān)聯(lián)起來捐寥。
echo "# andblog" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/WuXiaolong/andblog.git
git push -u origin master
關(guān)聯(lián) OK,后面修改祖驱,就直接使用 Android Studio 自帶的 Git 來提交代碼握恳。
接下來來看看 Flutter 網(wǎng)絡(luò)請求框架使用什么?怎么使用捺僻?
網(wǎng)絡(luò)
數(shù)據(jù)來源
說到網(wǎng)絡(luò)請求框架乡洼,首先要解決數(shù)據(jù)從何而來,我沒有后端(其實我可以開發(fā))匕坯,沒有服務(wù)器束昵,怎么搞?莫急葛峻,都說本系列文章是從零開發(fā) APP锹雏,且能一個人做一個項目,我自然有辦法术奖。
數(shù)據(jù)我使用的 Bmob礁遵,它可以創(chuàng)建你想要的表,支持 RestAPI采记,這可以為做 APP 省去后端開發(fā)成本佣耐,當(dāng)然像 Bmob 提供這樣的服務(wù)有很多,就不一一介紹唧龄,Bmob 如何使用兼砖,也不說了,官方有很詳細(xì)的文檔,你可以點擊文章底部「閱讀原文」注冊個賬號玩玩讽挟。
http
網(wǎng)絡(luò)請求框架的數(shù)據(jù)有了懒叛,可以玩起來了。
以請求文章列表接口示例耽梅,先用 Postman 看下數(shù)據(jù)結(jié)構(gòu):
{
"results": [
{
"content": "文章內(nèi)容測試1",
"cover": "http://pic1.win4000.com/wallpaper/2020-04-21/5e9e676001e20.jpg",
"createdAt": "2020-07-05 13:50:58",
"date": "2020.07.01",
"objectId": "ct7BGGGV",
"summary": "摘要1",
"title": "標(biāo)題測試1",
"updatedAt": "2020-07-05 13:53:16"
},
{
"content": "文章內(nèi)容測試2",
"cover": "http://pic1.win4000.com/wallpaper/2020-04-21/5e9e676001e20.jpg",
"createdAt": "2020-07-05 13:52:37",
"date": "2020.07.02",
"objectId": "3L42777G",
"summary": "摘要2",
"title": "標(biāo)題測試2",
"updatedAt": "2020-07-05 13:53:10"
}
]
}
Flutter 提供了網(wǎng)絡(luò)請求框架是 http薛窥,地址:https://pub.flutter-io.cn/packages/http
添加 http 包,在 pubspec.yaml 添加:
dependencies:
http: ^0.12.1
項目根目錄執(zhí)行命令flutter pub get
安裝軟件包褐墅。
新建 blog_list_page.dart 用來展示文章列表拆檬,blog.dart 是文章列表的結(jié)構(gòu)表洪己,把入口 main.dart 直接加載文章列表妥凳,詳細(xì)代碼如下。
main.dart:
import 'package:flutter/material.dart';
import 'andblog/list/blog_list_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AndBlog',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: new BlogListPage(),
);
}
}
blog_list_page.dart:
import 'package:flutter/material.dart';
import 'package:flutter_andblog/andblog/http/http_common.dart';
import 'package:http/http.dart' as http;
import 'blog.dart';
class BlogListPage extends StatefulWidget {
@override
BlogListPageState createState() => new BlogListPageState();
}
class BlogListPageState extends State<BlogListPage> {
List<Blog> blogList = [];
@override
void initState() {
super.initState();
//一進(jìn)頁面就請求接口
getBlogListData();
}
//網(wǎng)絡(luò)請求
getBlogListData() async {
var response = await http.get(HttpCommon.blog_list_url, headers: HttpCommon.headers());
if (response.statusCode == 200) {
// setState 相當(dāng)于 runOnUiThread
setState(() {
blogList = Blog.decodeData(response.body);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AndBlog'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
把網(wǎng)絡(luò) url 都放在 HttpCommon答捕,詳細(xì)代碼在 http_common.dart:
class HttpCommon{
static var blog_list_url = 'https://api2.bmob.cn/1/classes/ArticleTable/';
static Map<String, String> headers(){
//設(shè)置header
Map<String, String> headers = new Map();
headers["X-Bmob-Application-Id"] = "bmob Application-Id";
headers["X-Bmob-REST-API-Key"] = "bmob REST-API-Key";
headers["Content-Type"] = "application/json";
return headers;
}
}
網(wǎng)絡(luò)請求數(shù)據(jù)解析放在 blog.dart:
import 'dart:convert';
class Blog{
final String content;
final String cover;
final String date;
final String objectId;
final String summary;
final String title;
//構(gòu)造函數(shù)
Blog({
this.content,
this.cover,
this.date,
this.objectId,
this.summary,
this.title,
});
static List<Blog> decodeData(String jsonData) {
List<Blog> blogList = new List<Blog>();
var data = json.decode(jsonData);
var results = data['results'];
print('results='+results[0]['content']);
for (int i = 0; i < results.length; i++) {
blogList.add(fromMap(results[i]));
}
return blogList;
}
static Blog fromMap(Map<String, dynamic> map) {
return new Blog(
content: map['content'],
cover: map['cover'],
date: map['date'],
objectId: map['objectId'],
summary: map['summary'],
title: map['title'],
);
}
}
我習(xí)慣性打印print('results='+results[0]['content']);
看看數(shù)據(jù)解析對不對逝钥,多次嘗試最后打印文章內(nèi)容測試1
,達(dá)到了預(yù)期拱镐。
json_serializable
在寫文章列表的結(jié)構(gòu) blog.dart 需要手動一個個敲字段艘款,然后解析,F(xiàn)lutter 有沒有像 GsonFormat 這樣自動解析的插件沃琅,當(dāng)然是有哗咆,是 json_serializable,使用 json_serializable益眉,你需要一個常規(guī)依賴晌柬,以及兩個 dev 依賴:
dependencies:
flutter:
sdk: flutter
json_annotation: ^3.0.1
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.0
json_serializable: ^3.3.0
項目根目錄執(zhí)行命令flutter pub get
安裝軟件包。
以文章詳情結(jié)構(gòu)體示例:
{
"content": "文章內(nèi)容測試1",
"cover": "http://pic1.win4000.com/wallpaper/2020-04-21/5e9e676001e20.jpg",
"createdAt": "2020-07-05 13:50:58",
"date": "2020.07.01",
"objectId": "ct7BGGGV",
"summary": "摘要1",
"title": "標(biāo)題測試1",
"updatedAt": "2020-07-05 13:53:16"
}
根據(jù) json 創(chuàng)建實體類 detail.dart:
import 'package:json_annotation/json_annotation.dart';
//為了使實體類文件找到生成文件郭脂,需要 part 'detail.g.dart'
part 'detail.g.dart';
@JsonSerializable()
class Detail{
final String content;
final String cover;
final String date;
final String objectId;
final String summary;
final String title;
//構(gòu)造函數(shù)
Detail({
this.content,
this.cover,
this.date,
this.objectId,
this.summary,
this.title,
});
}
剛寫完 detail.g.dart 會報錯年碘,這是正常的!因為我們還沒生成解析文件展鸡。
接下來解析屿衅,項目根目錄執(zhí)行命令flutter packages pub run build_runner build
會發(fā)現(xiàn)生成一個 detail.g.dart 文件:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'detail.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Detail _$DetailFromJson(Map<String, dynamic> json) {
return Detail(
content: json['content'] as String,
cover: json['cover'] as String,
date: json['date'] as String,
objectId: json['objectId'] as String,
summary: json['summary'] as String,
title: json['title'] as String,
);
}
Map<String, dynamic> _$DetailToJson(Detail instance) => <String, dynamic>{
'content': instance.content,
'cover': instance.cover,
'date': instance.date,
'objectId': instance.objectId,
'summary': instance.summary,
'title': instance.title,
};
然后把這兩個方法放到 detail.dart:
factory Detail.fromJson(Map<String, dynamic> json) => _$DetailFromJson(json);
Map<String, dynamic> toJson() => _$DetailToJson(this);
接下來就可以調(diào)用 fromJson 方法解析網(wǎng)絡(luò)請求的數(shù)據(jù):
var data = json.decode(response.body);
detail = Detail.fromJson(data);
print('results='+detail.title);
這樣看下來,使用 json_serializable 并沒有方便多少莹弊,只是把解析字段省了涤久,最煩沒有把添加字段步驟自動化,比 GsonFormat 弱爆了忍弛。