0%

使用 Flutter 编写心情日志应用

去年参加 Google Developer Days 的时候就被好好安利了一波,这一周好好研究了以下,成功实现了从 dart 零基础到写出跨 iOS 和 Android 的应用。为了编译成 iOS App,我还不得不花了点时间在 g470 上安装了黑苹果,效果居然还不错。

安装配置 Flutter SDK

windows

首先从 flutter 官网上下载 flutter sdk, 无法访问的可以尝试 国内镜像, 下载下来是一个压缩包,解压到目录下

点击 flutter_console ,看到这样的界面就已经可以使用了。

为了在 cmd 中能够全局使用,我们可以把 flutter sdk 加入系统环境变量。打开 控制面板\系统和安全\系统 ,点击 高级系统设置–> 环境变量

点击 Path ,选择 编辑

在弹出的界面里面选择 新建 ,复制粘贴解压的 flutter\bin 目录到文本框内就可以了:

再试着打开 cmd , 输入 flutter doctor, 就能看到相应的输出。

Mac os

Mac os 上面的配置过程类似,下载好 SDK 后,将 flutter sdk 加入 path 变量即可。以 zsh shell 为例,编辑 ~/.zshrc,将 /flutter/bin 目录加到PATH 这一行就可以

1
export PATH=[PATH_TO_FLUTTER_GIT_DIRECTORY]/flutter/bin:$PATH

配置Xcode 实机调试

flutter 框架写出来的程序可以编译在 android 和 iOS 两大平台上运行。编译成 apk 的方法在 官网 上有详细的教程,这里记录一下在 Mac os 上利用 xcode 编译遇到的问题。

  1. flutter 配置部署需要依赖安装 cocoapods:
    1
    2
    3
    4
    brew update
    brew install --HEAD libimobiledevice
    brew install ideviceinstaller ios-deploy cocoapods
    pod setup
  2. 由于我的系统还停留在 10.12.6,因此应用商店上下载的 xcode 并不是最新版本,而在使用 11.3 系统的 iphone 真机调试的时候会报出警告 Could not locate device support files 解决办法也很简单,参考了这篇 博客 。不需要升级系统或者下载更新 xcode ,只需要拷贝一个文件即可: 从 github 上下载支持文件,然后将其拖放到 Contents-->Developer-->Platforms-->iPhoneOS.platform-->DeviceSupport/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport 相应的系统目录下就可以。
  3. 应用签名的问题。现在的 xcode 不需要开发者账户也能在实体设备上安装调试应用(但是发布到应用商店是必须要的),参看 这篇博客教程 ,添加个人 appStore 账户作为个人开发者也可以调试运行

Json 文件本地存储与读取

这个问题花了我一点时间,总体的结局思路是 dart object 和 json object之间的互转,再将本地 json 作为 String 写入文本文档。

文件 IO

dart 提供了内置的 io 库,flutter 上面也有 path_provider 插件。

  1. 安装 path_provider插件
    编辑 pubspec.yml 文件,加入依赖:
    1
    2
    3
    4
    5
    6
    7
    8
    dependencies:
    flutter:
    sdk: flutter

    # The following adds the Cupertino Icons font to your application.
    # Use with the CupertinoIcons class for iOS style icons.
    cupertino_icons: ^0.1.0
    path_provider: ^0.4.0
    点击 Android Studio 里面的 Packages get 之后,就会自动下载安装依赖。
  2. 获取应用程序目录和缓存目录,参考 官方示例
    1
    2
    3
    4
    5
    6
    7
    import 'dart:io';
    import 'package:path_provider/path_provider.dart';
    Directory tempDir = await getTemporaryDirectory();
    String tempPath = tempDir.path;

    Directory appDocDir = await getApplicationDocumentsDirectory();
    String appDocPath = appDocDir.path;

    读取 Json

    查看 dart 官方库里面的 json.dart 源代码,有如下的代码注释:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    * Parses the string and returns the resulting Json object.
    *
    * The optional [reviver] function is called once for each object or list
    * property that has been parsed during decoding. The `key` argument is either
    * the integer list index for a list property, the string map key for object
    * properties, or `null` for the final result. Like this:
    *
    * The default [reviver] (when not provided) is the identity function.
    */
    dynamic decode(String source, {reviver(Object key, Object value)}) {
    if (reviver == null) reviver = _reviver;
    if (reviver == null) return decoder.convert(source);
    return new JsonDecoder(reviver).convert(source);
    }
    可见 json.decode 是将符合 json 标准 的字符串转变为 object ,可以是 List, Map.

object 与 json

官方文档里面是这样描述的:

Decoding and encoding JSON
Decode a JSON-encoded string into a Dart object with jsonDecode():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// NOTE: Be sure to use double quotes ("),
// not single quotes ('), inside the JSON string.
// This string is JSON, not Dart.
var jsonString = '''
[
{"score": 40},
{"score": 80}
]
''';

var scores = jsonDecode(jsonString);
assert(scores is List);

var firstScore = scores[0];
assert(firstScore is Map);
assert(firstScore['score'] == 40);

Encode a supported Dart object into a JSON-formatted string with jsonEncode():

1
2
3
4
5
6
7
8
9
10
11
var scores = [
{'score': 40},
{'score': 80},
{'score': 100, 'overtime': true, 'special_guest': null}
];

var jsonText = jsonEncode(scores);
assert(jsonText ==
'[{"score":40},{"score":80},'
'{"score":100,"overtime":true,'
'"special_guest":null}]');

Only objects of type int, double, String, bool, null, List, or Map (with string keys) are directly encodable into JSON. List and Map objects are encoded recursively.

实现思路

  1. 在 NotePage 中使用 RadioListTile widget,利用用户的选择值和时间构造 json 数据:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    List<Widget> makeRadioList() {
    List<Widget> list = new List<Widget>();
    for (int i = -2; i < 3; i++) {
    list.add(new RadioListTile(
    value: i,
    title: new Text('心情 $i'),
    activeColor: Colors.deepOrange,
    groupValue: _selected,
    onChanged: (int value) {
    onChanged(value);
    },
    subtitle: new Text('心情值'),
    secondary: new Icon(Icons.favorite),
    ));
    }
    return list;
    }
  2. 将每次获取到的 json 数据添加到一个 List 之中。采用字符串手动拼接构建 List:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var moodObject =
    {
    'Time': key,
    'Value': value,
    };
    print("mood object is: " + moodObject.toString());
    var jsonText = json.encode(moodObject);
    // manually construct a List
    jsonText = '[' + jsonText + ']';

    后续TODO

    模块化和页面跳转

    界面优化

    接入野狗 SDK