Flutter体验 Day 22-Model

Model 资料层

在开发应用程序的过程里,我们通常会定义 Model 的类别用来处理资料结构或是资料储存上的使用。

举个简单的例子,定义一个 JSON { "name": "Leo" } 资料的 Model 类别

class User {
    User({
        required this.name,
    });

    final String name;

    factory User.fromJson(Map<String, dynamic> json) => User(
        name: json["name"],
    );

    Map<String, dynamic> toJson() => {
        "name": name,
    };
}


void main() {
  var userData = { "name": "Leo" };
  var user = User.fromJson(userData);
  print(user.toJson().toString());  // {name: Leo}
}

为什麽需要 Model 类别来处理资料?

  • 资料的安全性:在开发的过程我们虽然也可以从 Map 型别取资料,但是无法掌握 value 对应的真实型别

  • 资料的管理与维护:随着专案大小,定义清楚的 Model 类别有助於开发与维护的工作进行

我们来看看气象资料开放平台 API 回传的 json 资料结构大致如下,可以看到我们我们至少就有5层的 Model 需要定义

{
    "success": "true",
    "records": {
        "locations": [{
            "datasetDescription": "台湾各县市乡镇未来3天(72小时)逐3小时天气预报",
            "locationsName": "台北市",
            "dataid": "D0047-061",
            "location": [{
                "locationName": "中正区",
                "geocode": "63000050",
                "lat": "25.046058",
                "lon": "121.516565",
                "weatherElement": [{
                    "elementName": "Wx",
                    "description": "天气现象",
                    "time": [{
                        "startTime": "2021-09-16 06:00:00",
                        "endTime": "2021-09-16 09:00:00",
                        "elementValue": [{
                                "value": "短暂阵雨或雷雨",
                                "measures": "自定义 Wx 文字"
                            },
                            {
                                "value": "15",
                                "measures": "自定义 Wx 单位"
                            }
                        ]
                    }]
                }]
            }]
        }]
    }
}

这个 API 回传的资料结构包含了某地区、某时间、某天气因子、某单位…,在取用资料上相对麻烦,

我们可以透过 Model 类别来处理回传的资料格式,并定义一些方法来取得资料内容,如下:

    var data = await WetherAPI().fetch(service, parameters: params);

    // json_serializable
    var weather = WeatherModel.fromJson(data);

    // first 自定义属性用来取得第一笔
    var record = weather.records.first;

    // 自定义 element 方法用来取得天气因子
    var description = record.element("WeatherDescription").now.values[0].value;

    var wx = record.element("Wx").now.values[1].value;

建构 Model 工具

简单的资料结构我们可以手动写 Code 处理,不过实务上的资料结构通常比较复杂,我们可以使用一些工具协助创建 Model 类别,将开发时间留给业务逻辑的处理而不是资料的建模。

  • json_serializable - 需手动定义好资料的结构,可以自动产生序列化相关的程序码。

  • quicktype - 提供 json 的资料内容,会自动化产生 Model 的程序码,需检查上下内容是否符合自己预期。

气象 API 资料

透过自动化的转换,我们使用 quicktype 提供 json 格式自动化产生对应的 Model

  • WeatherAPIResponse
  • Records
  • RecordsLocation
  • LocationLocation
  • WeatherElement
  • Time
  • ElementValue

完整程序码如下:

// To parse this JSON data, do
//
//     final weatherApiResponse = weatherApiResponseFromJson(jsonString);

import 'package:meta/meta.dart';
import 'dart:convert';

WeatherApiResponse weatherApiResponseFromJson(String str) => WeatherApiResponse.fromJson(json.decode(str));

String weatherApiResponseToJson(WeatherApiResponse data) => json.encode(data.toJson());

class WeatherApiResponse {
    WeatherApiResponse({
        required this.success,
        required this.records,
    });

    final String success;
    final Records records;

    factory WeatherApiResponse.fromJson(Map<String, dynamic> json) => WeatherApiResponse(
        success: json["success"],
        records: Records.fromJson(json["records"]),
    );

    Map<String, dynamic> toJson() => {
        "success": success,
        "records": records.toJson(),
    };
}

class Records {
    Records({
        required this.locations,
    });

    final List<RecordsLocation> locations;

    factory Records.fromJson(Map<String, dynamic> json) => Records(
        locations: List<RecordsLocation>.from(json["locations"].map((x) => RecordsLocation.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "locations": List<dynamic>.from(locations.map((x) => x.toJson())),
    };
}

class RecordsLocation {
    RecordsLocation({
        required this.datasetDescription,
        required this.locationsName,
        required this.dataid,
        required this.location,
    });

    final String datasetDescription;
    final String locationsName;
    final String dataid;
    final List<LocationLocation> location;

    factory RecordsLocation.fromJson(Map<String, dynamic> json) => RecordsLocation(
        datasetDescription: json["datasetDescription"],
        locationsName: json["locationsName"],
        dataid: json["dataid"],
        location: List<LocationLocation>.from(json["location"].map((x) => LocationLocation.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "datasetDescription": datasetDescription,
        "locationsName": locationsName,
        "dataid": dataid,
        "location": List<dynamic>.from(location.map((x) => x.toJson())),
    };
}

class LocationLocation {
    LocationLocation({
        required this.locationName,
        required this.geocode,
        required this.lat,
        required this.lon,
        required this.weatherElement,
    });

    final String locationName;
    final String geocode;
    final String lat;
    final String lon;
    final List<WeatherElement> weatherElement;

    factory LocationLocation.fromJson(Map<String, dynamic> json) => LocationLocation(
        locationName: json["locationName"],
        geocode: json["geocode"],
        lat: json["lat"],
        lon: json["lon"],
        weatherElement: List<WeatherElement>.from(json["weatherElement"].map((x) => WeatherElement.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "locationName": locationName,
        "geocode": geocode,
        "lat": lat,
        "lon": lon,
        "weatherElement": List<dynamic>.from(weatherElement.map((x) => x.toJson())),
    };
}

class WeatherElement {
    WeatherElement({
        required this.elementName,
        required this.description,
        required this.time,
    });

    final String elementName;
    final String description;
    final List<Time> time;

    factory WeatherElement.fromJson(Map<String, dynamic> json) => WeatherElement(
        elementName: json["elementName"],
        description: json["description"],
        time: List<Time>.from(json["time"].map((x) => Time.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "elementName": elementName,
        "description": description,
        "time": List<dynamic>.from(time.map((x) => x.toJson())),
    };
}

class Time {
    Time({
        required this.startTime,
        required this.endTime,
        required this.elementValue,
    });

    final DateTime startTime;
    final DateTime endTime;
    final List<ElementValue> elementValue;

    factory Time.fromJson(Map<String, dynamic> json) => Time(
        startTime: DateTime.parse(json["startTime"]),
        endTime: DateTime.parse(json["endTime"]),
        elementValue: List<ElementValue>.from(json["elementValue"].map((x) => ElementValue.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "startTime": startTime.toIso8601String(),
        "endTime": endTime.toIso8601String(),
        "elementValue": List<dynamic>.from(elementValue.map((x) => x.toJson())),
    };
}

class ElementValue {
    ElementValue({
        required this.value,
        required this.measures,
    });

    final String value;
    final String measures;

    factory ElementValue.fromJson(Map<String, dynamic> json) => ElementValue(
        value: json["value"],
        measures: json["measures"],
    );

    Map<String, dynamic> toJson() => {
        "value": value,
        "measures": measures,
    };
}

今日成果

程序码

weather


<<:  认识资料库-关联和非关联式资料库

>>:  Kotlin Android 第25天,从 0 到 ML - TensorFlow Lite 功能与特色

Day24 - 铁人付外挂实作付款类别(三)- 接收回传资料

完成付款请求之後,接下来是准备好接收金流商回传资讯的 Response 类别,目前外挂的资料夹结构如...

Day 3 隐私三宝存在的意义

如同Day 2提到的角落生物,俗称隐私服务三宝之称的服务条款(Term of use/Term of...

[DAY25] Boxenn 小结

将几篇 Boxenn 相关文章整理成分类目录(范例 GitHub repository 建置中,完成...

Day4 第一个HTML网页制作

VS CODE安装好之後,就可以来认识HTML啦~ 开始写HTML前的步骤 首先,在桌面上新增一个资...

[Python]文字识别模型-PaddleOCR

今天刚好找到一个有趣的文字识别模型,想来和大家介绍一下~ PaddleOCR PaddleOCR是百...