- 原文地址:Develop your first Application with Flutter
- 原文做者:Gahfy
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:mysterytony
- 校对者:rockzhai, zhaochuanxing
一周前,Flutter 在巴塞罗那的 MWC 上发布了初版公测版本。本文的主要目的是向你展现如何用 Flutter 开发第一个功能齐全的应用程序。前端
这篇文章会介绍 Flutter 的安装过程和工做原理,因此会比平时长一点。android
咱们将开发一个向用户显示从 JSONPlaceholder API 中检索的帖子列表的应用程序。ios
Flutter 是一款 SDK,它可让你开发基于 Android,iOS 或者 Google 的下一个操做系统 Fuschia 的原生应用。它使用 Dart 做为主要编程语言。git
为了获取 Flutter,你须要克隆其官方仓库。若是你想开发 Android 应用,则还须要 Android Studio 。若是要开发 iOS 应用,则还须要 XCode 。github
你还须要 IntelliJ IDEA(这不是必须的,可是会颇有用)。安装完 IntelliJ IDEA 以后,把 Dart 和 Flutter 插件添加到 IntelliJ IDEA。编程
你所要作的就是克隆 Flutter 官方仓库:json
git clone -b beta https://github.com/flutter/flutter.git
复制代码
而后,你须要将把 bin 文件夹的路径添加到 PATH 环境变量中。就这样,你如今能够开始用 Flutter 开发应用程序了。后端
虽然这已经足够了,为了避免让这篇文章显得冗长,我缩短了安装过程的讲解。若是你须要更完整的指南,请转至 官方文档。数组
让咱们如今打开 IntelliJ IDEA 并建立第一个项目。在左侧面板中,选择 Flutter (若是没有,就请将 Flutter 和 Dart 插件安装到你的 IDE 中)。bash
咱们以如下方式命名:
IntelliJ 的编辑器打开了一个名为 main.dart
的文件,它是应用程序的主文件。若是你还不了解 Dart,别慌,这个教程的剩下部分不时必须的。
如今,将 Android 或 iOS 手机插入你的计算机,或运行一个模拟器。
你如今能够经过点击右上角的运行按钮(带有绿色三角形)来运行该应用程序:
点击底部浮动动做按钮来增长显示的数字。咱们如今不会深刻研究其代码,但咱们会用 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
的文档告诉咱们至少要初始化 home
,routes
,onGenerateRoute
或者 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());
复制代码
你如今已经能够运行你的应用程序。目前只是一个没有任何内容的白色界面。因此咱们如今要作的第一件事就是添加一些用户界面。
咱们可能要开发两种用户界面。一种是与当前应用状态无关的用户界面,而另外一种是与当前状态相关的用户界面。
当谈到状态时,咱们的意思是,当事件被触发时,用户界面可能会改变,这正是咱们要作的:
目前,咱们只用了 StatelessWidget
,正如你所猜想的那样,它并不涉及程序状态。那么让咱们先初始化一个 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 的列表很是快,以至它几乎当即消失。
为了确保实际显示循环进度条,让咱们从 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 得到。
若是你喜欢这篇文章,你能够关注 个人推特 来得到下一篇的推送。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。