APP 開發從 0 到 1(二)框架與網路

吳小龍同學發表於2020-07-19

框架

之前做 APP 開發的時候,我都是擔任 Android 組 leader,新專案起來,我會做技術預研,如《一套完整的 Android 通用框架》,一般會使用 MVP 模式(現在應該是 MVVM 模式),網路請求框架使用 Retrofit,圖片載入使用 Glide,圖片縮放和裁剪分別使用 PhotoView 和 uCrop 等,必要時,我會寫個 sample 放專案裡,讓同事可以參考。

APP 開發從 0 到 1(二)框架與網路

這個也是個新專案,我也需要做下技術預研,Flutter 網路請求框架需要使用什麼?圖片載入又使用什麼?文章詳情,我打算使用 Markdown,這 Flutter 能實現嗎?等等,這些都是需要事前做好調研。

這個專案,程式碼版本管理用 GitHub,首先新建一個 Flutter 專案,GitHub 也新建個私有專案(暫時不公開吧),用如下命令將原生程式碼和遠端 GitHub 關聯起來。

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
複製程式碼

關聯 OK,後面修改,就直接使用 Android Studio 自帶的 Git 來提交程式碼。

接下來來看看 Flutter 網路請求框架使用什麼?怎麼使用?

網路

資料來源

說到網路請求框架,首先要解決資料從何而來,我沒有後端(其實我可以開發),沒有伺服器,怎麼搞?莫急,都說本系列文章是從零開發 APP,且能一個人做一個專案,我自然有辦法。

資料我使用的 Bmob,它可以建立你想要的表,支援 RestAPI,這可以為做 APP 省去後端開發成本,當然像 Bmob 提供這樣的服務有很多,就不一一介紹,Bmob 如何使用,也不說了,官方有很詳細的文件,你可以點選文章底部「閱讀原文」註冊個賬號玩玩。

http

網路請求框架的資料有了,可以玩起來了。

以請求文章列表介面示例,先用 Postman 看下資料結構:

{
    "results": [
        {
            "content": "文章內容測試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": "標題測試1",
            "updatedAt": "2020-07-05 13:53:16"
        },
        {
            "content": "文章內容測試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": "標題測試2",
            "updatedAt": "2020-07-05 13:53:10"
        }
    ]
}
複製程式碼

Flutter 提供了網路請求框架是 http,地址:pub.flutter-io.cn/packages/ht…

新增 http 包,在 pubspec.yaml 新增:

dependencies:
  http: ^0.12.1
複製程式碼

專案根目錄執行命令flutter pub get安裝軟體包。

新建 blog_list_page.dart 用來展示文章列表,blog.dart 是文章列表的結構表,把入口 main.dart 直接載入文章列表,詳細程式碼如下。

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();
    //一進頁面就請求介面
    getBlogListData();
  }

  //網路請求
  getBlogListData() async {
    var response = await http.get(HttpCommon.blog_list_url, headers: HttpCommon.headers());
    if (response.statusCode == 200) {
      // setState 相當於 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.
    );
  }
}
複製程式碼

把網路 url 都放在 HttpCommon,詳細程式碼在 http_common.dart:

class HttpCommon{

  static var blog_list_url = 'https://api2.bmob.cn/1/classes/ArticleTable/';

  static Map<String, String> headers(){
    //設定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;
  }

}
複製程式碼

網路請求資料解析放在 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;

  //建構函式
  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'],
    );
  }
}
複製程式碼

我習慣性列印print('results='+results[0]['content']);看看資料解析對不對,多次嘗試最後列印文章內容測試1,達到了預期。

json_serializable

在寫文章列表的結構 blog.dart 需要手動一個個敲欄位,然後解析,Flutter 有沒有像 GsonFormat 這樣自動解析的外掛,當然是有,是 json_serializable,使用 json_serializable,你需要一個常規依賴,以及兩個 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
複製程式碼

專案根目錄執行命令flutter pub get安裝軟體包。

以文章詳情結構體示例:

{
    "content": "文章內容測試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": "標題測試1",
    "updatedAt": "2020-07-05 13:53:16"
}
複製程式碼

根據 json 建立實體類 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;

  //建構函式
  Detail({
    this.content,
    this.cover,
    this.date,
    this.objectId,
    this.summary,
    this.title,
  });
}
複製程式碼

剛寫完 detail.g.dart 會報錯,這是正常的!因為我們還沒生成解析檔案。

接下來解析,專案根目錄執行命令flutter packages pub run build_runner build

會發現生成一個 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);
複製程式碼

接下來就可以呼叫 fromJson 方法解析網路請求的資料:

var data = json.decode(response.body);
detail = Detail.fromJson(data);
print('results='+detail.title);
複製程式碼

這樣看下來,使用 json_serializable 並沒有方便多少,只是把解析欄位省了,最煩沒有把新增欄位步驟自動化,比 GsonFormat 弱爆了。

APP 開發從 0 到 1(二)框架與網路

相關文章