從網路擷取資料
從網路獲取資料對於大多數應用程式來說是必要的。幸運的是,Dart 和 Flutter 提供了工具,例如 http
套件,來進行這種類型的工作。
此範例使用以下步驟
- 新增
http
套件。 - 使用
http
套件發出網路請求。 - 將回應轉換為自訂 Dart 物件。
- 使用 Flutter 獲取並顯示資料。
1. 新增 http
套件
#http
套件提供從網際網路獲取資料的最簡單方式。
要將 http
套件新增為依賴項,請執行 flutter pub add
flutter pub add http
匯入 http 套件。
import 'package:http/http.dart' as http;
如果您要部署到 Android,請編輯您的 AndroidManifest.xml
檔案以新增網際網路權限。
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
同樣地,如果您要部署到 macOS,請編輯您的 macos/Runner/DebugProfile.entitlements
和 macos/Runner/Release.entitlements
檔案以包含網路用戶端授權。
<!-- Required to fetch data from the internet. -->
<key>com.apple.security.network.client</key>
<true/>
2. 發出網路請求
#此範例說明如何使用 http.get()
方法從 JSONPlaceholder 獲取範例專輯。
Future<http.Response> fetchAlbum() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
}
http.get()
方法會傳回一個包含 Response
的 Future
。
Future
是一個核心 Dart 類別,用於處理非同步作業。Future 物件表示未來某個時間點可能可用的值或錯誤。http.Response
類別包含從成功的 http 呼叫接收到的資料。
3. 將回應轉換為自訂 Dart 物件
#雖然發出網路請求很容易,但使用原始的 Future<http.Response>
並不是很方便。為了讓您的生活更輕鬆,請將 http.Response
轉換為 Dart 物件。
建立 Album
類別
#首先,建立一個 Album
類別,其中包含來自網路請求的資料。它包含一個工廠建構函式,可從 JSON 建立 Album
。
使用 模式比對 轉換 JSON 只是一種選項。如需更多資訊,請參閱關於 JSON 和序列化 的完整文章。
class Album {
final int userId;
final int id;
final String title;
const Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'userId': int userId,
'id': int id,
'title': String title,
} =>
Album(
userId: userId,
id: id,
title: title,
),
_ => throw const FormatException('Failed to load album.'),
};
}
}
將 http.Response
轉換為 Album
#現在,使用以下步驟更新 fetchAlbum()
函式以傳回 Future<Album>
- 使用
dart:convert
套件將回應主體轉換為 JSONMap
。 - 如果伺服器確實傳回狀態碼為 200 的 OK 回應,則使用
fromJson()
工廠方法將 JSONMap
轉換為Album
。 - 如果伺服器未傳回狀態碼為 200 的 OK 回應,則擲回例外狀況。(即使在「404 Not Found」伺服器回應的情況下,也要擲回例外狀況。不要傳回
null
。這在檢查snapshot
中的資料時很重要,如下所示。)
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
太棒了!現在您有一個從網際網路獲取專輯的函式。
4. 獲取資料
#在 initState()
或 didChangeDependencies()
方法中呼叫 fetchAlbum()
方法。
initState()
方法只會被呼叫一次,之後就不會再被呼叫。如果您希望能夠在回應 InheritedWidget
變更時重新載入 API,請將呼叫放入 didChangeDependencies()
方法中。如需更多詳細資料,請參閱 State
。
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
// ···
}
此 Future 會在下一步中使用。
5. 顯示資料
#若要在畫面上顯示資料,請使用 FutureBuilder
小工具。FutureBuilder
小工具隨附於 Flutter,可讓您輕鬆處理非同步資料來源。
您必須提供兩個參數
- 您想要處理的
Future
。在此範例中,是從fetchAlbum()
函式傳回的 future。 - 一個
builder
函式,根據Future
的狀態(載入中、成功或錯誤)告訴 Flutter 要呈現什麼。
請注意,只有當快照包含非空資料值時,snapshot.hasData
才會傳回 true
。
因為 fetchAlbum
只能傳回非空值,所以即使在「404 Not Found」伺服器回應的情況下,該函式也應該擲回例外狀況。擲回例外狀況會將 snapshot.hasError
設定為 true
,可用於顯示錯誤訊息。
否則,將會顯示微調器。
FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
)
為何在 initState() 中呼叫 fetchAlbum()?
#雖然很方便,但不建議將 API 呼叫放入 build()
方法中。
Flutter 會在每次需要變更檢視中的任何內容時呼叫 build()
方法,而且這種情況發生的頻率非常高。如果將 fetchAlbum()
方法放置在 build()
內部,則會在每次重建時重複呼叫,導致應用程式速度變慢。
將 fetchAlbum()
結果儲存在狀態變數中,可確保 Future
只會執行一次,然後快取以供後續重建使用。
測試
#如需如何測試此功能的相關資訊,請參閱以下範例
完整範例
#import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
const Album({
required this.userId,
required this.id,
required this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'userId': int userId,
'id': int id,
'title': String title,
} =>
Album(
userId: userId,
id: id,
title: title,
),
_ => throw const FormatException('Failed to load album.'),
};
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
除非另有說明,否則本網站上的文件反映了最新穩定版的 Flutter。頁面上次更新時間為 2024-09-16。 檢視原始碼 或 回報問題。