[译] 用 Flutter 开发你的第一个应用程序

用 Flutter 开发你的第一个应用程序

一周前,Flutter 在巴塞罗那的 MWC 上发布了初版公测版本。本文的主要目的是向你展现如何用 Flutter 开发第一个功能齐全的应用程序。前端

这篇文章会介绍 Flutter 的安装过程和工做原理,因此会比平时长一点。android

咱们将开发一个向用户显示从 JSONPlaceholder API 中检索的帖子列表的应用程序。ios

什么是 Flutter ?

Flutter 是一款 SDK,它可让你开发基于 Android,iOS 或者 Google 的下一个操做系统 Fuschia 的原生应用。它使用 Dart 做为主要编程语言。git

安装所需的工具

Git,Android Studio 和 XCode

为了获取 Flutter,你须要克隆其官方仓库。若是你想开发 Android 应用,则还须要 Android Studio 。若是要开发 iOS 应用,则还须要 XCode 。github

IntelliJ IDEA

你还须要 IntelliJ IDEA(这不是必须的,可是会颇有用)。安装完 IntelliJ IDEA 以后,把 Dart 和 Flutter 插件添加到 IntelliJ IDEA。编程

获取 Flutter

你所要作的就是克隆 Flutter 官方仓库:json

git clone -b beta https://github.com/flutter/flutter.git
复制代码

而后,你须要将把 bin 文件夹的路径添加到 PATH 环境变量中。就这样,你如今能够开始用 Flutter 开发应用程序了。后端

虽然这已经足够了,为了避免让这篇文章显得冗长,我缩短了安装过程的讲解。若是你须要更完整的指南,请转至 官方文档数组

开发第一个项目

让咱们如今打开 IntelliJ IDEA 并建立第一个项目。在左侧面板中,选择 Flutter (若是没有,就请将 Flutter 和 Dart 插件安装到你的 IDE 中)。bash

咱们以如下方式命名:

  • 项目名称: feedme
  • 描述: A sample JSON API project
  • 组织: net.gahfy
  • Android 语言: Kotlin
  • iOS 语言: Swift

运行第一个项目并探索 Flutter

IntelliJ 的编辑器打开了一个名为 main.dart 的文件,它是应用程序的主文件。若是你还不了解 Dart,别慌,这个教程的剩下部分不时必须的。

如今,将 Android 或 iOS 手机插入你的计算机,或运行一个模拟器。

你如今能够经过点击右上角的运行按钮(带有绿色三角形)来运行该应用程序:

点击底部浮动动做按钮来增长显示的数字。咱们如今不会深刻研究其代码,但咱们会用 Flutter 发现一些有趣的功能。

Flutter 热重载

你能够看到,这个应用的主要颜色是蓝色。咱们能够改为红色。在 main.dart 文件中,找到如下代码:

return new MaterialApp(
  title: 'Flutter Demo',
  theme: new ThemeData(
    // This is the theme of your application.
    //
    // Try running your application with "flutter run". You'll see the
    // application has a blue toolbar. Then, without quitting the app, try
    // changing the primarySwatch below to Colors.green and then invoke
    // "hot reload" (press "r" in the console where you ran "flutter run",
    // or press Run > Flutter Hot Reload in IntelliJ). Notice that the
    // counter didn't reset back to zero; the application is not restarted.
    primarySwatch: Colors.blue,
  ),
  home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
复制代码

在这个部分,用 Colors.red 来代替 Colors.blue。Flutter 容许你热加载应用程序,也就是说应用程序的当前状态不会被修改,可是会使用新的代码。

在应用程序中,点击底部浮动的 + 按钮开增长 counter 。

而后,在 IntelliJ 右上角,点击 Hot Reload 按钮(带有黄色闪电)。你能够开到主要的颜色变成了红色,可是 counter 保持着同样的数字。

开发最终的应用程序

让咱们如今删除 main.dart 文件里全部内容,这岂不是一个更好的学习方式吗。

最小的应用程序

咱们要作的第一件事就是开发最小的应用程序,也就是能运行的最少代码。由于咱们会用 Material Design 来设计咱们的应用程序,因此首先要导入包含 Material Design Widgets 的包。

import 'package:flutter/material.dart';
复制代码

如今咱们来建立一个继承 StatelessWidget 的类来建立咱们应用程序的一个实例(以后会深刻讨论 StatelessWidget)。

import 'package:flutter/material.dart';
 
class MyApp extends StatelessWidget {
 
}
复制代码

IntelliJ IDEA 在 MyApp 下显示红色下划线。实际上 StatelessWidget 是一个须要实现 build() 方法的抽象类。为此,将光标移动到 MyApp 上,而后按 Alt + Enter 。

import 'package:flutter/material.dart';
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
  }
}
复制代码

如今咱们来实现 build() 方法,咱们能够看到它必须返回一个 Widget 实例。咱们要在这里构建应用程序时返回一个 MaterialApp。为此,在 build() 中添加如下代码:

return new MaterialApp();
复制代码

MaterialApp 的文档告诉咱们至少要初始化 homeroutesonGenerateRoute 或者 builder 。咱们只会在这里定义 home 属性。这将是应用程序的主界面。由于咱们但愿咱们的应用程序是基于 Material Design 的布局,因此咱们把 home 设置为一个空的 Scaffold

import 'package:flutter/material.dart';
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        home: new Scaffold()
    );
  }
}
复制代码

最后咱们须要设置当运行 main.dart 时,咱们想运行 MyApp 应用程序。所以,咱们须要在导入语句后面添加如下行:

void main() => runApp(new MyApp());
复制代码

你如今已经能够运行你的应用程序。目前只是一个没有任何内容的白色界面。因此咱们如今要作的第一件事就是添加一些用户界面。

开发用户界面

几句关于状态的话

咱们可能要开发两种用户界面。一种是与当前应用状态无关的用户界面,而另外一种是与当前状态相关的用户界面。

当谈到状态时,咱们的意思是,当事件被触发时,用户界面可能会改变,这正是咱们要作的:

  • 应用程序启动事件: - 显示循环进度条
    • 运行检索帖子的操做
  • API 请求结束:
    • 若是成功,显示检索帖子的结果
    • 若是失败, 在空白界面上显示带失败信息的 Snackbar

目前,咱们只用了 StatelessWidget,正如你所猜想的那样,它并不涉及程序状态。那么让咱们先初始化一个 StatefulWidget

初始化 StatefulWidget

让咱们添加一个继承 StatefulWidget 的类到咱们的应用程序:

import 'package:flutter/material.dart';
 
void main() => runApp(new MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        home: new PostPage()
    );
  }
}
 
class PostPage extends StatefulWidget {
  PostPage({Key key}) : super(key: key);
 
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
  }
}
复制代码

像咱们看到的同样,咱们须要实现返回一个 State 对象的 createState() 方法。因此让咱们建立一个继承 State 的类:

class PostPage extends StatefulWidget {
  PostPage({Key key}) : super(key: key);
 
  @override
  _PostPageState createState() => new _PostPageState();
}
 
class _PostPageState extends State<PostPage>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
  }
}
复制代码

就像看到的,咱们须要实现 build() 方法,让它返回一个 Widget 。为此,咱们先建立一个空部件 (Row):

class _PostPageState extends State<PostPage>{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('FeedMe'),
        ),
        body: new Row()//TODO add the widget for current state
    );
  }
}
复制代码

咱们事实上返回了一个 Scaffold 对象,由于咱们应用程序的工具栏不会改变,也不依赖于当前状态。只是他的 body 会取决于当前状态。

让咱们如今建立一个方法,它将返回 Widget 以显示当前状态,以及一种返回一个包含居中的循环进度条的 Widget 的方法:

class _PostPageState extends State<PostPage>{
  Widget _getLoadingStateWidget(){
    return new Center(
      child: new CircularProgressIndicator(),
    );
  }
 
  Widget getCurrentStateWidget(){
    Widget currentStateWidget;
    currentStateWidget = _getLoadingStateWidget();
    return currentStateWidget;
  }
 
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('FeedMe'),
        ),
        body: getCurrentStateWidget()
    );
  }
}
复制代码

若是你如今运行这个应用程序,你会看到一个居中的循环进度条。

显示帖子列表

咱们先定义 Post 对象,由于它是在 JSONPlaceholder API 中定义的。为此,建立一个包含如下内容的 Post.dart 文件:

class Post {
  final int userId;
 
  final int id;
 
  final String title;
 
  final String body;
 
  Post({
    this.userId,
    this.id,
    this.title,
    this.body
  });
}
复制代码

如今咱们在同一个文件中定义一个 PostState 类来设计应用程序的当前状态:

class PostState{
  List<Post> posts;
  bool loading;
  bool error;
 
  PostState({
    this.posts = const [],
    this.loading = true,
    this.error = false,
  });
 
  void reset(){
    this.posts = [];
    this.loading = true;
    this.error = false;
  }
}
复制代码

如今要作的就是在 PostState 类中定义一个方法来从 API 中获取 Post 的列表。稍后咱们将看到如何作到这一点,由于如今咱们只能异步地返回一个静态的 Post 列表:

Future<void> getFromApi() async{
  this.posts = [
    new Post(userId: 1, id: 1, title: "Title 1", body: "Content 1"),
    new Post(userId: 1, id: 2, title: "Title 2", body: "Content 2"),
    new Post(userId: 2, id: 3, title: "Title 3", body: "Content 3"),
  ];
  this.loading = false;
  this.error = false;
}
复制代码

如今完成了,让咱们回到 main.dart 文件中的 PostPageState 类来看看如何使用咱们刚定义的类。咱们在 PostPageState 类中初始化一个 postState 属性:

class _PostPageState extends State<PostPage>{
  final PostState postState = new PostState();
 
  // ...
}
复制代码

若是 IntelliJ IDEA 在 PostState 下显示红色下划线,这意味着 PostState 类没有在当前文件中定义。因此你须要导入它。将光标移至红色下划线部分,而后按Alt + Enter,而后选择导入。

如今,让咱们定义一个方法,当咱们成功获取 Post 列表时就返回一个 Widget :

Widget _getSuccessStateWidget(){
  return new Center(
    child: new Text(postState.posts.length.toString() + " posts retrieved")
  );
}
复制代码

若是咱们成功得到 Post 的列表,如今要作的就是编辑 getCurrentStateWidget() 方法来显示这个 Widget :

Widget getCurrentStateWidget(){
  Widget currentStateWidget;
  if(!postState.error && !postState.loading) {
    currentStateWidget = _getSuccessStateWidget();
  }
  else{
    currentStateWidget = _getLoadingStateWidget();
  }
  return currentStateWidget;
}
复制代码

最后要作的,也许最重要的一件事就是运行请求以检索 Post 的列表。为此,定义一个 _getPosts() 方法并在初始化状态时调用它:

@override
void initState() {
  super.initState();
  _getPosts();
}
 
_getPosts() async {
  if (!mounted) return;
 
  await postState.getFromApi();
  setState((){});
}
复制代码

当当当,你能够运行应用程序来看结果。实际上,即便真的显示了循环进度条,也几乎没有机会看获得。这是由于检索 Post 的列表很是快,以至它几乎当即消失。

从 API 中检索帖子列表

为了确保实际显示循环进度条,让咱们从 JSONPlaceholder API 中检索该帖子。若是咱们看一下 API 的 post 服务,咱们能够看到它返回一个帖子的 JSON 数组。

所以,咱们必须先为 Post 类添加一个静态方法,以便将 Post 的 JSON 数组转换为 Post 列表:

static List<Post> fromJsonArray(String jsonArrayString){
  List data = JSON.decode(jsonArrayString);
  List<Post> result = [];
  for(var i=0; i<data.length; i++){
    result.add(new Post(
        userId: data[i]["userId"],
        id: data[i]["id"],
        title: data[i]["title"],
        body: data[i]["body"]
    ));
  }
  return result;
}
复制代码

咱们如今只需编辑检索 PostState 类中的 Post 列表的方法,让它从 API 真正地检索帖子:

Future<void> getFromApi() async{
  try {
    var httpClient = new HttpClient();
    var request = await httpClient.getUrl(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
    var response = await request.close();
    if (response.statusCode == HttpStatus.OK) {
      var json = await response.transform(UTF8.decoder).join();
      this.posts = Post.fromJsonArray(json);
      this.loading = false;
      this.error = false;
    }
    else{
      this.posts = [];
      this.loading = false;
      this.error = true;
    }
  } catch (exception) {
    this.posts = [];
    this.loading = false;
    this.error = true;
  }
}
复制代码

你如今能够运行该应用程序,根据网速或多或少地能够看到循环进度条。

显示帖子列表

目前,咱们只显示检索的帖子数量,但不会像咱们预期的那样显示帖子列表。为了可以显示它,让咱们编辑 PostPageState 类的 _getSuccessStateWidget() 方法:

Widget _getSuccessStateWidget(){
  return new ListView.builder(
    itemCount: postState.posts.length,
    itemBuilder: (context, index) {
      return new Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          new Text(postState.posts[index].title,
            style: new TextStyle(fontWeight: FontWeight.bold)),
 
          new Text(postState.posts[index].body),
 
          new Divider()
        ]
      );
    }
  );
}
复制代码

若是再次运行应用程序,你就会看到帖子列表。

处理错误

咱们还有最后一件事要作:处理错误。您能够尝试在飞行模式下运行应用程序,而后就能够看到无限循环进度条。因此咱们要返回一个空白错误:

Widget _getErrorState(){
  return new Center(
    child: new Row(),
  );
}
 
Widget getCurrentStateWidget(){
  Widget currentStateWidget;
  if(!postState.error && !postState.loading) {
    currentStateWidget = _getSuccessStateWidget();
  }
  else if(!postState.error){
    currentStateWidget = _getLoadingStateWidget();
  }
  else{
    currentStateWidget = _getErrorState();
  }
  return currentStateWidget;
}
复制代码

如今,当发生错误时,它会显示一个空白的界面。你能够随意更改内容来显示错误界面。可是咱们说过,咱们但愿显示一个 Snackbar,以便在出现错误时重试。为此,让咱们在 PostPageState 类中开发 showError()retry() 方法:

class _PostPageState extends State<PostPage>{
  // ...
  BuildContext context;
 
  // ...
  _retry(){
    Scaffold.of(context).removeCurrentSnackBar();
    postState.reset()
    setState((){});
    _getPosts();
  }
 
  void _showError(){
    Scaffold.of(context).showSnackBar(new SnackBar(
      content: new Text("An unknown error occurred"),
      duration: new Duration(days: 1), // Make it permanent
      action: new SnackBarAction(
        label : "RETRY",
        onPressed : (){_retry();}
      )
    ));
  }
 
  //...
}
复制代码

正如咱们所看到的,咱们须要一个 BuildContext 来得到 ScaffoldState,它可让 Snackbar 出现并消失。可是咱们必须使用 Scaffold 对象的 BuildContext 来得到 ScaffoldState 。为此,咱们须要编辑 PostPageState 类的 build() 方法:

Widget currentWidget = getCurrentStateWidget();
return new Scaffold(
    appBar: new AppBar(
      title: new Text('FeedMe'),
    ),
    body: new Builder(builder: (BuildContext context) {
      this.context = context;
      return currentWidget;
    })
);
复制代码

如今在飞行模式下运行你的应用程序,它如今就会显示 Snackbar 了。若是您离开飞行模式,而后点击重试,就能够看到帖子了。

总结

咱们了解了用 Flutter 开发一个功能齐全的应用程序并不困难。全部 Material Design 的元素都是被提供的,而且就在刚刚,你用它们在 Android 和 iOS 平台上开发了一个应用程序。

该项目的全部源代码都可在 Feed-Me Flutter project on GitHub 得到。


若是你喜欢这篇文章,你能够关注 个人推特 来得到下一篇的推送。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索