今天和大家一起來看看在flutter中如何進行網際資料交換,分為以下部分:
Future
Future
相關功能http
套件進行flutter networking好的,那我們就開始吧!
表示未來某個時間內獲得的資料。
當我們呼叫一個非同步函式時,會回傳一個「未完成的Future」來等待此函式的非同步行為執行完畢,並獲得結果,也可能在執行的過程中出現問題而回傳錯誤
Future<int>
代表這個非同步行為回傳的資料型別是int
,也可以使用自定義的類別如
Future<List<OrderDetail>> // 資料的型別是「由OrderDetail組成的List」
常見使用Future.delayed
constructor來建立一個延遲時間的Future:`
// 等待兩秒之後印出 Here I come!
Future.delayed(const Duration(seconds: 2), () => print('Here I come!'));
表示執行了一個行為並獲得執行結果後,接續執行的下一個行為:
final server = connectToServer();
server
// 向server要求資料回應
.post(myUrl, fields: const {'name': 'Dash', 'profession': 'mascot'})
.then(handleResponse) // 接下來,處理server的回應
final server = connectToServer();
server
.post(myUrl, fields: const {'name': 'Dash', 'profession': 'mascot'})
.then(handleResponse)
.catchError(handleError) // 攔下錯誤並透過 handleError function處理
final server = connectToServer();
server
.post(myUrl, fields: const {'name': 'Dash', 'profession': 'mascot'})
.then(handleResponse)
.catchError(handleError)
.whenComplete(server.close); // 不管是獲得response或出現error,最後都將server關閉
httpRequest.timeout(const Duration (seconds:5),onTimeout :(){
return 'timeout!'
});
如上例,此httpRequest
執行超過五秒之後,會收到一個預設值:'timeout!'
將非同步取得的資料結合UI顯示在頁面上,可以依照當前非同步行為的狀態改變UI的呈現
FutureBuilder({
this.future,
this.initialData,
required this.builder,
})
Function (BuildContext context, AsyncSnapshot snapshot)
context
是當前頁面情境所包含的資訊,snapshot
包含了這次非同步任務的資訊snapshot.connectionState
(目前的非同步任務執行狀態), snapshot.hasError
(是否產生錯誤), ...等等寫法: 以官網案例說明
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
); // 在兩秒後顯示 'Data Loaded'
FutureBuilder<String>(
future: _calculation, // 執行_calculation 內定義的非同步任務
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
// 若是成功執行_calculation並獲取回傳的內容,則顯示children UI如下
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
),
];
} else if (snapshot.hasError) {
// 若是在_calculation執行過程中出現錯誤,則顯示children UI如下
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
// 預設顯示的children UI
children = const <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
),
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
},
),
和.then
, .catchError
是不同的概念,目的是將一連串的非同步行為轉為同步方式處理
async
:將function標示為非同步函式,在function內部可以使用await
await
: 將function內各個非同步的步驟轉為同步,會等待該步驟的執行結果,才開始下一個步驟
Future<void> printDailyNewsDigest() async {
try {
var newsDigest = await gatherNewsReports();
print(newsDigest);
} catch (e) {
// Handle error...
}
}
如上例,我們已知gatherNewsReports
的行為無法及時完成,需要一段時間;使用await
會在這邊等到gatherNewsReport
回傳資料或報錯,才會繼續執行下一步
http
package進行網際資料交換http
紀錄到專案中的pubspec.yaml
,日後看pubspec.yaml
就知道這個專案的運作需要http套件。
$ flutter pub add http
$ flutter pub get
android/app/src/main/AndroidManifest.xml
,也就是Android app的設定檔上加入
<manifest xmlns:android...>
...
<!--加入這行-->
<uses-permission android:name="android.permission.INTERNET" />
<application ...
</manifest>
使用範例:例如我要在專案中進行一個post request
import 'package:http/http.dart' as http; // 在檔案中引用http套件
var url = Uri.https('example.com', 'whatsit/create');
var response = await http.post(url, body: {'name': 'doodle', 'color': 'blue'}); // 透過http套件進行post request
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
print(await http.read(Uri.https('example.com', 'foobar.txt')));
在專案中加入http
package
$ flutter pub add http
$ flutter pub get
到 NASA OPEN API 官網申請自己的API KEY,並記錄下來
找到 APOD API 並詳閱下方的document
在專案中的lib
之中建立 model
資料夾,存放data model
在model中新增 ApodData.dart
建立class ApodData。看過APOD api document之後,我們知道每次request會回傳以下資料
{
"date": "2022-10-01",
"explanation": "Observe the Moon every night and you'll see its visible sunlit portion gradually change. In phases progressing from New Moon to Full Moon to New Moon again, a lunar cycle or lunation is completed in about 29.5 days. Top left to bottom right, this 7x4 matrix of telescopic images captures the range of lunar phases for 28 consecutive nights, from the evening of July 29 to the morning of August 26, following an almost complete lunation. No image was taken 24 hours or so just after and just before New Moon, when the lunar phase is at best a narrow crescent, close to the Sun and really hard to see. Finding mostly clear Mediterranean skies required an occasional road trip to complete this lunar cycle project, imaging in early evening for the first half and late evening and early morning for the second half of the lunation. Since all the images are registered at the same scale you can use this matrix to track the change in the Moon's apparent size during the single lunation. For extra credit, find the lunar phase that occurred closest to perigee. Tonight: International Observe the Moon Night",
"hdurl": "https://apod.nasa.gov/apod/image/2210/Lu20220729-0826.jpg",
"media_type": "image",
"service_version": "v1",
"title": "Lunation Matrix",
"url": "https://apod.nasa.gov/apod/image/2210/Lu20220729-0826_1050.jpg"
}
所以我們將必要的資料加入ApodData
中
class ApodData {
final String title; // 圖片標題
final String url; // 圖片資源連結
final String mediaType; // 圖片類型
final String desc; // 圖片描述
final String date; // 日期
ApodData(this.title, this.url, this.mediaType, this.desc, this.date);
ApodData.fromJson(Map<String, dynamic> json)
: title = json['title'],
url = json['hdurl'],
mediaType = json['media_type'],
desc = json['explanation'],
date = json['date'];
Map<String, dynamic> toJson() => {
'title': title,
'url': url,
'media_type': mediaType,
'explanation': desc,
'date': date,
};
}
在main_page建立 call api的function。記得將api key使用gitignore隱藏起來
class _MainPageState extends State<MainPage> {
final String apodUrl = 'https://api.nasa.gov/planetary/apod';
@override
void initState() {
_fetchDailyApodData(); // 在頁面生成時取得APOD 資訊
super.initState();
}
Future<ApodData?> _fetchDailyApodData() async {
Uri url = Uri.parse('$apodUrl?api_key=$apiKey&thumbs=true');
final response = await http.get(url, headers: {
'Content-type': 'application/json',
'Accept': 'application/json',
});
final parsedResponse = json.decode(response.body) as Map<String, dynamic>;
return ApodData.fromJson(parsedResponse);
}
@override
Widget build(BuildContext context) {
Size deviceScreen = MediaQuery.of(context).size;
return SingleChildScrollView(
child: FutureBuilder(
future: _fetchDailyApodData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
ApodData? data = snapshot.data;
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
data != null ? data.title : '',
style: const TextStyle(
fontSize: 30, fontWeight: FontWeight.w500),
),
),
Stack(
children: [
SizedBox(
width: deviceScreen.width,
child: data != null
? Image.network(data.url, frameBuilder:
(context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) {
return child;
}
return AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
child: child,
);
})
: SizedBox(
width: deviceScreen.width,
height: deviceScreen.width,
child: const Center(
child: Text(
'圖片載入錯誤',
style:
TextStyle(color: Colors.red, fontSize: 30),
)),
),
),
Positioned(
top: 10.0,
right: 10.0,
child: ElevatedButton(
onPressed: () {
print('add to favorite');
},
child: const Text('favorite')),
),
],
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
data != null ? data.desc : '',
style:
const TextStyle(fontSize: 16, color: Colors.blueGrey),
),
),
],
);
}
if (snapshot.hasError) {
return const Center(
child: Text(
'頁面載入錯誤',
style: TextStyle(color: Colors.red, fontSize: 30),
));
}
return SizedBox(
height: deviceScreen.height,
width: deviceScreen.width,
child: const Center(child: CircularProgressIndicator()));
},
),
);
}
}
Day14
相關commit今天大致瞭解了
Future.then
: 完成一個非同步行為後接著進行的動作Future.catchError
:攔截過程中出現的問題Future.whenComplete
: 無論非同步任務執行成功與否,最後要執行的動作Future.timeout
: 設定非同步任務的逾時時間以及對應的行為FutureBuilder
: 結合非同步任務變更UIasync
await
處理非同步行為http
package的使用方式明天一起來看看如何用Icon
, TextInput
, List
等組件構成頁面吧~