跨平台技术篇 - Flutter 设备和 SDK API

目录:

  1. 使用 packages
  2. 开发 Packages 和插件
  3. 使用平台通道编写平台特定的代码
  4. 读写文件
  5. 在 Flutter 中发起 HTTP 网络请求

1. 使用 packages

Flutter 支持使用由其他开发者贡献给 Flutter 和 Dart 生态系统的共享软件包。这使你可以快速构建应用程序,而无需从头开始开发所有应用程序。现有的软件包支持许多使用场景,例如,网络请求 (http),自定义导航/路由处理 (fluro), 集成设备 API (如url_launcher&battery) 以及使用第三方平台 SDK (如 Firebase (需翻墙)))。

如果你正打算开发新的软件包,请参阅开发软件包。如果你希望添加资源、图片或字体,无论是存储在文件还是包中,请参阅资源和图片。

  • 1.1 使用包

搜索 packages,packages 会被发布到了 Pub 包仓库。Flutter landing 页面显示了与 Flutter 兼容的包 (即声明依赖通常与扑兼容),所有已发布的包都支持搜索。将包依赖项添加到应用程序,要将包 "css_colors" 添加到应用中,请执行以下操作:

  • 依赖它,打开 pubspec.yaml 文件,然后在 dependencies 下添加 css_colors。
  • 安装它,在 terminal 中: 运行 flutter packages get 或者在 IntelliJ 中: 点击 pubspec.yaml 文件顶部的 "Packages Get"。
  • 导入它,在你的 Dart 代码中添加相应的 import 语句。
  • 1.2 开发新的 packages

如果某个软件包不适用于你的特定需求,则可以开发新的自定义 package (下一章)。

扫描二维码关注公众号,回复: 5957346 查看本文章
  • 1.3 管理包依赖和版本

Package versions

所有软件包都有一个版本号,在它们的 pubspec.yaml 文件中指定。Pub 会在其名称旁边显示软件包的当前版本 (例如,请参阅 url_launcher 软件包) 以及所有先前版本的列表。

当 pubspec.yaml 使用速记形式添加包时,plugin1: 这被解释为 plugin1:any,即可以使用任何版本的包。为了确保某个包在更新后还可以正常使用,我们建议使用以下格式之一指定版本范围:

  • 范围限制: 指定一个最小和最大的版本号,如:
  dependencies:
      url_launcher: '>=0.1.2 <0.2.0'
  • 范围限制使用 caret 语法:  与常规的范围约束类似 (译者语:这和 node 下 npm 的版本管理类似)
dependencies:
  collection: '^0.1.2'

有关更多详细信息,请参阅 Pub 版本管理指南

更新依赖包

当你在添加一个包后首次运行 (IntelliJ 中的 "Packages Get") flutter packages get,Flutter 将找到包的版本保存在pubspec.lock。这确保了如果你或你的团队中的其他开发人员运行 flutter packages get 后回获取相同版本的包。

如果要升级到软件包的新版本,例如使用该软件包中的新功能,请运行 flutter packages upgrade (在 IntelliJ 中点击Upgrade dependencies)。 这将根据你在 pubspec.yaml 中指定的版本约束下载所允许的最高可用版本。

 

依赖未发布的 packages

即使未在 Pub 上发布,软件包也可以使用。对于不用于公开发布的专用插件,或者尚未准备好发布的软件包,可以使用其他依赖项选项:

  • 路径 依赖: 一个 Flutter 应用可以依赖一个插件通过文件系统的 path: 依赖。路径可以是相对的,也可以是绝对的。例如,要依赖位于应用相邻目录中的插件 "plugin1",请使用以下语法:
dependencies:
  plugin1:
    path: ../plugin1/
  • Git 依赖: 你也可以依赖存储在 Git 仓库中的包。如果软件包位于仓库的根目录中,请使用以下语法:
dependencies:
  plugin1:
    git:
      url: git://github.com/flutter/plugin1.git
  • Git 依赖于文件夹中的包: 默认情况下,Pub 假定包位于 Git 存储库的根目录中。如果不是这种情况,你可以使用 path 参数指定位置,例如:
dependencies:
  package1:
    git:
      url: git://github.com/flutter/packages.git
      path: packages/package1    

最后,你可以使用 ref 参数将依赖关系固定到特定的 git commit,branch 或 tag。有关更多详细信息,请参阅 Pub Dependencies article

2. 开发 Packages 和插件

  • 2.1 Package 介绍

使用 package 可以创建可轻松共享的模块化代码。一个最小的 package 包括:

  • 一个 pubspec.yaml 文件:声明了 package 的名称、版本、作者等的元数据文件。
  • 一个 lib 文件夹:包括包中公开的 (public) 代码,最少应有一个 <package-name>.dart 文件。
  • 2.2 Package 类型

Packages 可以包含多种内容:

  • Dart 包:其中一些可能包含 Flutter 的特定功能,因此对 Flutter 框架具有依赖性,仅将其用于 Flutter,例如 fluro 包。
  • 插件包:一种专用的 Dart 包,其中包含用 Dart 代码编写的 API,以及针对 Android (使用 Java 或 Kotlin) 和/或针对 iOS (使用 ObjC 或 Swift) 平台的特定实现。一个具体的例子是 battery 插件包。
  • 2.3 Developing Dart packages

Step 1: 开发 Dart 包

要创建 Dart 包,请使用 --template=package 来执行 flutter create。这将在 hello/ 文件夹下创建一个具有以下专用内容的 package 工程:

  • lib/hello.dart:
    • Package 的 Dart 代码。
  • test/hello_test.dart:

Step 2: 实现 package

对于纯 Dart 包,只需在主 lib/<package name>.dart 文件内或 lib 目录中的文件中添加功能 。要测试软件包,请在 test目录中添加 unit tests。有关如何组织包内容的更多详细信息,请参阅 Dart library package 文档。

  • 2.4 开发插件包

如果你想开发一个调用特定平台 API 的包,你需要开发一个插件包,插件包是 Dart 包的专用版本。 插件包包含针对Android (Java 或 Kotlin 代码) 或 iOS (Objective-C 或 Swift 代码) 编写的特定于平台的实现 (可以同时包含 Android 和 Ios 原生的代码)。 API使用 platform channels 连接到特定平台 (Android 或 IOS)。

Step 1: 创建 package

要创建插件包,请使用 --template=plugin 参数执行 flutter create。使用 --org 选项指定你的组织,并使用反向域名表示法。该值用于生成的 Android 和 iOS 代码中的各种包和包标识符。

flutter create --org com.example --template=plugin hello

这将在 hello/ 文件夹下创建一个具有以下专用内容的插件工程:

  • lib/hello.dart:
    • 插件包的 Dart API。
  • android/src/main/java/com/yourcompany/​hello/HelloPlugin.java:
    • 插件包 API 的 Android 实现。
  • ios/Classes/HelloPlugin.m:
    • 插件包 API 的 ios 实现。
  • example/:
    • 一个依赖于该插件的 Flutter 应用程序,来说明如何使用它。

默认情况下,插件项目针对 iOS 代码使用 Objective-C,Android 代码使用 Java。如果你更喜欢 Swift 或 Kotlin,则可以使用 -i 或 -a 为 iOS 或 Android 指定语言。例如:

flutter create --template=plugin -i swift -a kotlin hello

Step 2: 实现包 package

由于插件包中包含用多种编程语言编写的多个平台的代码,因此需要一些特定的步骤来确保顺畅的体验。

Step 2a: 定义包 API (.dart),插件包的 API 在 Dart 代码中定义。打开主文件夹 hello/,找到 lib/hello.dart。

Step 2b: 添加 Android 平台代码 (.java / .kt),我们建议你使用 Android Studio 编辑 Android 代码。在 Android Studio 中编辑Android 平台代码之前,首先确保代码至少已经构建过一次 (例如,从 IntelliJ 运行示例应用程序或在终端执行 cd hello/exampleflutter build apk)。

接下来:

  1. 启动 Android Studio。
  2. 在 "Welcome to Android Studio" 对话框选择 "Import project",或者在菜单栏 "File > New > Import Project…",然后选择hello/example/android/build.gradle 文件。
  3. 在 "Gradle Sync" 对话框, 选择 "OK"。
  4. 在 "Android Gradle Plugin Update" 对话框,选择 "Don’t remind me again for this project"。

你插件的 Android 平台代码位于 hello/java/com.yourcompany.hello/​HelloPlugin。你可以通过按下 ▶ 按钮从 Android Studio 运行示例应用程序。

Step 2c: 添加 iOS 平台代码 (.h+.m/.swift),我们建议你使用 Xcode 编辑 iOS 代码 。在编辑 Xcode 中的 iOS 平台代码之前,首先确保代码至少已经构建过一次 (例如,从 Xcode 中运行示例应用程序或终端执行 cd hello/exampleflutter build ios --no-codesign)

接下来:

  1. 启动 Xcode。
  2. 选择 "File > Open",然后选择 hello/example/ios/Runner.xcworkspace 文件。

你插件的 iOS 平台代码位于 Pods/DevelopmentPods/hello/Classes/ 中。你可以通过按下 &#9654 按钮来运行示例应用程序。

Step 2d: 连接 API 和平台代码,最后,你需要将用 Dart 代码编写的 API 与平台特定的实现连接起来,这是通过 platform channels 完成的。

  • 2.5 添加文档

建议将以下文档添加到所有软件包:

  1. README.md: 介绍包的文件。
  2. CHANGELOG.md: 记录每个版本中的更改。
  3. LICENSE: 包含软件包许可条款的文件。
  4. 所有公共 API 的 API 文档 (详情见下文)。

API documentation

在发布软件包时,API 文档会自动生成并发布到 dartdocs.org,示例请参阅 device_info docs。如果你希望在本地生成 API 文档,请使用以下命令:

  • 将目录更改为您的软件包的位置: cd ~/dev/mypackage。
  • 告诉文档工具 Flutter SDK 的位置:
export FLUTTER_ROOT=~/dev/flutter (on macOS or Linux)

set FLUTTER_ROOT=~/dev/flutter (on Windows)
  • 运行 dartdoc 工具 (它是 Flutter SDK 的一部分):
$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dartdoc (on macOS or Linux)

%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dartdoc (on Windows)

有关如何编写 API 文档的提示,请参阅 Effective Dart: Documentation

  • 2.6 发布 packages

一旦你实现了一个包,你可以在 Pub 上发布它 ,这样其他开发人员就可以轻松使用它。在发布之前,检查pubspec.yamlREADME.md以及CHANGELOG.md 文件,以确保其内容的完整性和正确性。然后,运行 dry-run 命令以查看是否都准备 OK 了:

flutter packages pub publish --dry-run

最后, 运行发布命令:

flutter packages pub publish

有关发布的详细信息,请参阅 Pub publishing docs

  • 2.7 处理包的相互依赖

如果您正在开发一个 hello 包,它依赖于另一个包,则需要将该依赖包添加到 pubspec.yaml 文件的 dependencies 部分。 下面的代码使 url_launcher 插件的 Dart API ,这在 hello 包中是可用的:

In hello/pubspec.yaml:

dependencies:
  url_launcher: ^0.4.2

现在你可以在 hello 中 import 'package:url_launcher/url_launcher.dart' 然后 launch(someUrl) 了。这与在Flutter 应用程序或任何其他 Dart 项目中引用软件包没有什么不同。但是,如果 hello 碰巧是一个插件包,其平台特定的代码需要访问 url_launcher 公开的特定于平台的 API,那么你还需要为特定于平台的构建文件添加合适的依赖声明,如下所示。

Android

在 hello/android/build.gradle:

android {
    // lines skipped
    dependencies {
        provided rootProject.findProject(":url_launcher")
    }
}

你现在可以在 hello/android/src 源码中 import io.flutter.plugins.urllauncher.UrlLauncherPlugin 访问UrlLauncherPlugin 类。

iOS

在 hello/ios/hello.podspec:

Pod::Spec.new do |s|
  # lines skipped
  s.dependency 'url_launcher'

你现在可以在 hello/ios/Classes 源码中 #import "UrlLauncherPlugin.h" 然后访问 UrlLauncherPlugin 类。

 

解决冲突

假设你想在你的 hello 包中使用 some_package 和 other_package,并且这两个包都依赖 url_launcher,但是依赖的是 url_launcher 的不同的版本。那我们就有潜在的冲突,避免这种情况的最好方法是在指定依赖关系时,程序包作者使用版本范围而不是特定版本。

dependencies:
  url_launcher: ^0.4.2    # Good, any 0.4.x with x >= 2 will do.
  image_picker: '0.1.1'   # Not so good, only 0.1.1 will do.

如果 some_package 声明了上面的依赖关系,other_package 声明了 url_launcher 版本像 "0.4.5" 或 "^0.4.0",pub 将能够自动解决问题。 类似的注释适用于插件包对 Gradle 模块和 Cocoa pods 的平台特定的依赖关系。

即使 some_package 和 other_package 声明了不兼容的 url_launcher 版本,它仍然可能会和 url_launcher 以兼容的方式正常工作。 你可以通过向 hello 包的 pubspec.yaml 文件中添加依赖性覆盖声明来处理冲突,从而强制使用特定版本:

强制使用 0.4.3 版本的 url_launcher,在  hello/pubspec.yaml 中:

dependencies:
  some_package:
  other_package:
dependency_overrides:
  url_launcher: '0.4.3'

如果冲突的依赖不是一个包,而是一个特定于 Android 的库,比如 guava,那么必须将依赖重写声明添加到 Gradle 构建逻辑中。强制使用23.0版本的 guava 库,在 hello/android/build.gradle 中:

configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:23.0-android'
    }
}

Cocoapods 目前不提供依赖覆盖功能。

3. 使用平台通道编写平台特定的代码

所谓 "平台特定"或"特定平台",平台指的就是原生 Android 或 IOS,本文主要讲原生和 Flutter 之间如何通信、如何进行功能互调。

本指南介绍如何编写自定义平台特定的代码。一些平台特定的功能可通过现有软件包获得,请参阅使用 packages

Flutter 使用了一个灵活的系统,允许你调用特定平台的 API,无论在 Android 上的 Java 或 Kotlin 代码中,还是 iOS 上的ObjectiveC 或 Swift 代码中均可用。

Flutter 平台特定的 API 支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:

  • 应用的 Flutter 部分通过平台通道  (platform channel) 将消息发送到其应用程序的所在的宿主 (iOS 或 Android)。
  • 宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的 API (使用原生编程语言) - 并将响应发送回客户端,即应用程序的 Flutter 部分。
  • 3.1 框架概述: 平台通道

使用平台通道在客户端 (Flutter UI) 和宿主 (平台) 之间传递消息,如下图所示:

Platform channels architecture

消息和响应是异步传递的,以确保用户界面保持响应 (不会挂起)。

在客户端,MethodChannel (API) 可以发送与方法调用相对应的消息。 在宿主平台上,MethodChannel 在 Android((API) 和 FlutterMethodChannel iOS (API) 可以接收方法调用并返回结果。这些类允许你用很少的"脚手架"代码开发平台插件。

平台通道数据类型支持和解码器

标准平台通道使用标准消息编解码器,以支持简单的类似 JSON 值的高效二进制序列化,例如 booleans,numbers, Strings, byte buffers, List, Maps (请参阅 StandardMessageCodec 了解详细信息)。 当你发送和接收值时,这些值在消息中的序列化和反序列化会自动进行。

下表显示了如何在宿主上接收 Dart 值,反之亦然:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
int, if 64 bits not enough java.math.BigInteger FlutterStandardBigInteger
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary
  • 3.2 示例: 使用平台通道调用 iOS 和 Android 代码

以下演示如何调用平台特定的 API 来获取和显示当前的电池电量。它通过一个平台消息 getBatteryLevel 调用 Android BatteryManager API 和 iOS device.batteryLevel API。

该示例在应用程序内添加了特定于平台的代码。如果你想开发一个通用的平台包,可以在其它应用中也使用的话,你需要开发一个插件,则项目创建步骤稍有不同 (请参阅 开发 packages),但平台通道代码仍以相同方式编写。

注意: 此示例的完整的可运行源代码位于:/examples/platform_channel/, 这个示例 Android 是用的 Java, IOS 用的是 Objective-C,IOS Swift 版本请参阅 /examples/platform_channel_swift/

Step 1: 创建一个新的应用程序项目

首先创建一个新的应用程序:

  • 在终端运行中:flutter create batterylevel。

默认情况下,模板支持使用 Java 编写 Android 代码,或使用 Objective-C 编写 iOS 代码。要使用 Kotlin 或 Swift,请使用 -i 和 /或 -a 标志:

  • 在终端中运行: flutter create -i swift -a kotlin batterylevel。

Step 2: 创建 Flutter 平台客户端

该应用的 State 类拥有当前的应用状态,我们需要延长这一点以保持当前的电量。首先,我们构建通道。我们使用 MethodChannel 调用一个方法来返回电池电量。

通道的客户端和宿主通过通道构造函数中传递的通道名称进行连接。单个应用中使用的所有通道名称必须是唯一的; 我们建议在通道名称前加一个唯一的"域名前缀",例如 samples.flutter.io/battery

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.io/battery');

  // Get battery level.
}

接下来,我们调用通道上的方法,指定通过字符串标识符调用方法 getBatteryLevel。 该调用可能失败 - 例如,如果平台不支持平台 API (例如在模拟器中运行时),所以我们将 invokeMethod 调用包装在 try-catch 语句中。我们使用返回的结果,在 setState 中来更新用户界面状态 batteryLevel

  // Get battery level.
  String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

最后,我们在 build 创建包含一个小字体显示电池状态和一个用于刷新值的按钮的用户界面。

@override
Widget build(BuildContext context) {
  return new Material(
    child: new Center(
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          new RaisedButton(
            child: new Text('Get Battery Level'),
            onPressed: _getBatteryLevel,
          ),
          new Text(_batteryLevel),
        ],
      ),
    ),
  );
}

Step 3a: 使用 Java 添加 Android 平台特定的实现

注意: 以下步骤使用 Java。如果你更喜欢 Kotlin,请跳到步骤 3b。

首先在 Android Studio 中打开你的 Flutter 应用的 Android 部分:

  • 启动 Android Studio。
  • 选择 "File > Open…"。
  • 定位到你 Flutter app 目录,然后选择里面的 android 文件夹,点击 OK。
  • 在 java 目录下打开 MainActivity.java。

接下来,在 onCreate 里创建 MethodChannel 并设置一个 MethodCallHandler。确保使用与在 Flutter 客户端使用的通道名称相同。

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.io/battery";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // TODO
                    }
                });
    }
}

接下来,我们添加 Java 代码,使用 Android 电池 API 来获取电池电量。此代码与你在原生 Android 应用中编写的代码完全相同,首先,添加需要导入的依赖:

import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;

然后,将下面的新方法添加到 activity 类中的,位于 onCreate 方法下方:

private int getBatteryLevel() {
  int batteryLevel = -1;
  if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
    BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
    batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
  } else {
    Intent intent = new ContextWrapper(getApplicationContext()).
        registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
        intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
  }

  return batteryLevel;
}

最后,我们完成之前添加的 onMethodCall 方法。我们需要处理平台方法名为 getBatteryLevel,所以我们在 call 参数中进行检测是否为 getBatteryLevel。 这个平台方法的实现只需调用我们在前一步中编写的 Android 代码,并使用 response 参数返回成功和错误情况的响应。如果调用未知的方法,我们也会通知返回:

@Override
public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("getBatteryLevel")) {
        int batteryLevel = getBatteryLevel();

        if (batteryLevel != -1) {
            result.success(batteryLevel);
        } else {
            result.error("UNAVAILABLE", "Battery level not available.", null);
        }
    } else {
        result.notImplemented();
    }
}               

你现就可以在 Android 上运行该应用程序。如果你使用的是 Android 模拟器,则可以通过工具栏中的...按钮访问 Extended Controls 面板中的电池电量。

  • 3.3 从 UI 代码中分离平台特定的代码

如果你希望在多个 Flutter 应用程序中使用特定于平台的代码,将代码分离为位于主应用程序之外的目录中,做一个平台插件会很有用,详情请参阅开发 packages 。

  • 3.4 将平台特定的代码作为一个包发布

如果你希望与 Flutter 生态系统中的其他开发人员分享你的特定平台的代码,请参阅发[发布 packages](/developing-packages/#publish 以了解详细信息。

  • 3.5 自定义平台通道和编解码器

除了上面提到的 MethodChannel,你还可以使用 BasicMessageChannel,它支持使用自定义消息编解码器进行基本的异步消息传递。 此外,你可以使用专门的 BinaryCodecStringCodec 和 JSONMessageCodec 类,或创建自己的编解码器。

4. 读写文件

本指南介绍如何使用 PathProvider 插件和 Dart 的 IO 库在 Flutter 中读写文件 。

  • 4.1 介绍

PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:

  • 临时目录:  系统可随时清除的临时目录 (缓存)。在 iOS 上,这对应于 NSTemporaryDirectory() 返回的值。在 Android上,这是 getCacheDir()返回的值。
  • 文档目录:  应用程序的目录,用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在 iOS上,这对应于 NSDocumentDirectory。在 Android 上,这是 AppData 目录。

一旦你的 Flutter 应用程序有一个文件位置的引用,你可以使用 dart:io API来执行对文件系统的读/写操作。有关使用 Dart 处理文件和目录的更多信息,请参阅此概述和这些示例

  • 4.2 读写文件的示例

以下示例展示了如何统计应用程序中按钮被点击的次数(关闭重启数据不丢失):

  • 通过 flutter create 或在 IntelliJ 中 File > New Project 创建一个新 Flutter App。
  • 在 pubspec.yaml 文件中声明依赖 PathProvider 插件。
  • 用一下代码替换 lib/main.dart 中的:
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(
    new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: new FlutterDemo(),
    ),
  );
}

class FlutterDemo extends StatefulWidget {
  FlutterDemo({Key key}) : super(key: key);

  @override
  _FlutterDemoState createState() => new _FlutterDemoState();
}

class _FlutterDemoState extends State<FlutterDemo> {
  int _counter;

  @override
  void initState() {
    super.initState();
    _readCounter().then((int value) {
      setState(() {
        _counter = value;
      });
    });
  }

  Future<File> _getLocalFile() async {
    // get the path to the document directory.
    String dir = (await getApplicationDocumentsDirectory()).path;
    return new File('$dir/counter.txt');
  }

  Future<int> _readCounter() async {
    try {
      File file = await _getLocalFile();
      // read the variable as a string from the file.
      String contents = await file.readAsString();
      return int.parse(contents);
    } on FileSystemException {
      return 0;
    }
  }

  Future<Null> _incrementCounter() async {
    setState(() {
      _counter++;
    });
    // write the variable as a string to the file
    await (await _getLocalFile()).writeAsString('$_counter');
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text('Flutter Demo')),
      body: new Center(
        child: new Text('Button tapped $_counter time${
          _counter == 1 ? '' : 's'
        }.'),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}

5. 在 Flutter 中发起 HTTP 网络请求

本页介绍如何在 Flutter 中创建 HTTP 网络请求。对于 socket,请参阅 dart:io

注:本篇文档官方使用的是用 dart io 中的 HttpClient 发起的请求,但 HttpClient 本身功能较弱,很多常用功能都不支持。我们建议你使用 dio 来发起网络请求,它是一个强大易用的 dart http 请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传/下载……详情请查看 github dio

  • 5.1 发起 HTTP 请求

http 支持位于 dart:io,所以要创建一个 HTTP client, 我们需要添加一个导入:

import 'dart:io';

var httpClient = new HttpClient();

该 client 支持常用的 HTTP 操作,such as GETPOSTPUTDELETE

  • 5.2 处理异步

注意,HTTP API 在返回值中使用了 Dart Futures。 我们建议使用 async/await 语法来调用 API。网络调用通常遵循如下步骤:

  1. 创建 client。
  2. 构造 Uri。
  3. 发起请求, 等待请求,同时你也可以配置请求 headers、 body。
  4. 关闭请求, 等待响应。
  5. 解码响应的内容。

Several of these steps use Future based APIs. Sample APIs calls for each step above are: 其中的几个步骤使用基于 Future 的API。上面步骤的示例:

get() async {
  var httpClient = new HttpClient();
  var uri = new Uri.http(
      'example.com', '/path1/path2', {'param1': '42', 'param2': 'foo'});
  var request = await httpClient.getUrl(uri);
  var response = await request.close();
  var responseBody = await response.transform(UTF8.decoder).join();
}
  • 5.3 解码和编码 JSON

使用 dart:convert 库可以简单解码和编码 JSON。 有关其他的 JSON 文档,请参阅 JSON 和序列化。解码简单的 JSON 字符串并将响应解析为 Map:

Map data = JSON.decode(responseBody);
// Assume the response body is something like: ['foo', { 'bar': 499 }]
int barValue = data[1]['bar']; // barValue is set to 499

要对简单的 JSON 进行编码,请将简单值 (字符串,布尔值或数字字面量) 或包含简单值的 Map,list 等传给 encode 方法:

String encodedString = JSON.encode([1, 2, { 'a': null }]);
  • 5.4 示例: 解码 HTTPS GET 请求的 JSON

以下示例显示了如何在 Flutter 应用中对 HTTPS GET 请求返回的 JSON 数据进行解码。

It calls the httpbin.com web service testing API, which then responds with your local IP address. Note that secure networking (HTTPS) is used. 它调用 httpbin.comWeb service 测试 API,请注意,使用安全网络请求 (HTTPS)。

  • 运行 flutter create,创建一个新的 Flutter 应用。
  • 将 lib/main.dart 替换为一下内容:
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _ipAddress = 'Unknown';

  _getIPAddress() async {
    var url = 'https://httpbin.org/ip';
    var httpClient = new HttpClient();

    String result;
    try {
      var request = await httpClient.getUrl(Uri.parse(url));
      var response = await request.close();
      if (response.statusCode == HttpStatus.OK) {
        var json = await response.transform(UTF8.decoder).join();
        var data = JSON.decode(json);
        result = data['origin'];
      } else {
        result =
            'Error getting IP address:\nHttp status ${response.statusCode}';
      }
    } catch (exception) {
      result = 'Failed getting IP address';
    }

    // If the widget was removed from the tree while the message was in flight,
    // we want to discard the reply rather than calling setState to update our
    // non-existent appearance.
    if (!mounted) return;

    setState(() {
      _ipAddress = result;
    });
  }

  @override
  Widget build(BuildContext context) {
    var spacer = new SizedBox(height: 32.0);

    return new Scaffold(
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text('Your current IP address is:'),
            new Text('$_ipAddress.'),
            spacer,
            new RaisedButton(
              onPressed: _getIPAddress,
              child: new Text('Get IP address'),
            ),
          ],
        ),
      ),
    );
  }
}

猜你喜欢

转载自blog.csdn.net/u014294681/article/details/89016410