- 原文地址: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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。