FutureBuilder và StreamBuilder trong Flutter
- 11-06-2024
- Toanngo92
- 0 Comments
Mục lục
FutureBuilder trong Flutter
Có một khái niệm gọi là lập trình bất đồng bộ, nơi mà hàm không thể tạo ra kết quả ngay lập tức. Nó cần một thời gian để lấy hoặc xử lý và trả về dữ liệu. Một hàm bình thường sẽ xử lý đầu vào và tạo ra đầu ra của bất kỳ loại nào. Trong khi đó, các hàm bất đồng bộ này sẽ xử lý và tạo ra đầu ra của loại Future, về cơ bản chứa đầu ra của bất kỳ loại nào.
Dưới đây là một số ví dụ để giúp bạn hiểu rõ hơn.
Một hàm thực hiện phép cộng giữa hai số sẽ thực thi ngay lập tức.
Nó sẽ hoạt động và trả về kết quả ngay lập tức. Đây là một hàm đồng bộ.
Tuy nhiên, hãy xem xét việc mở một ứng dụng mạng xã hội như Instagram. Khi ứng dụng mở, nó sẽ tải dữ liệu, bạn có thể thấy một chỉ báo tải đang được hiển thị, cho biết các bài đăng trên Newsfeed đang được lấy. Khi dữ liệu đã được lấy, trang chính sẽ được điền vào với các bài đăng và câu chuyện. Đây là một ví dụ về một hàm bất đồng bộ.
Một số ví dụ khác là tìm kiếm Google, lấy dữ liệu từ cơ sở dữ liệu, tải lên tập tin từ thiết bị, và như vậy.
FutureBuilder Widget trong Flutter
Flutter có một widget gọi là FutureBuilder
giúp hiển thị dữ liệu Future một cách dễ dàng.
Cú pháp của lớp FutureBuilder
:
FutureBuilder<T>(
key: key,
future: future,
initialData: initialData,
builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
// Trả về widget dựa trên snapshot
},
)
Các đối số quan trọng cần xem xét từ mã là:
Future<T>? future
: Đây là nơi bạn chỉ định hàm Future trả về một giá trị của kiểu T (int, String, bất cứ thứ gì).builder
: Đây là nơi bạn xây dựng dữ liệu để hiển thị. Hai đối số Context và snapshot được truyền vào đây. Snapshot là một loại có sẵn trong Flutter thông qua đó bạn có thể xử lý dữ liệu.
Một hàm bất đồng bộ có thể không luôn trả về dữ liệu. Có khả năng nó có thể trả về một lỗi, mất nhiều thời gian để tải, và như vậy. Đây là nơi một snapshot giúp chúng ta.
Các Trạng thái trong FutureBuilder
Như đã thấy trước đó, một hàm bất đồng bộ cần thời gian để trả về dữ liệu, vì vậy cần chỉ ra cho người dùng biết liệu nó đang tải, đã được lấy hay có lỗi gì đó. Trong Flutter, có các trạng thái để biểu thị những điều này và được gọi là ConnectionStates. Các ConnectionStates khác nhau trong Flutter như sau:
ConnectionState.waiting
: Kết nối với tính toán bất đồng bộ và đang chờ đợi, tương tác hoặc nói cách khác. Dữ liệu đang được lấy và quá trình chưa hoàn thành.ConnectionState.done
: Kết nối với tính toán bất đồng bộ đã được kết thúc hoặc nói cách khác là dữ liệu đã được lấy hoặc trả về một lỗi, nhưng kết nối đã đóng.ConnectionState.active
: Kết nối với một tính toán bất đồng bộ đang hoạt động. Kết nối đã được thiết lập. Điều này lý tưởng cho streams.ConnectionState.none
: Hiện không kết nối với bất kỳ tính toán bất đồng bộ nào, nghĩa là không có hàm Future hoặc stream nào có sẵn để kết nối.
Tạo ứng dụng để Thể hiện FutureBuilder trong Flutter
Để hiểu rõ hơn về tất cả các khái niệm, hãy tạo một ứng dụng Flutter với một hàm Future sẽ trả về thời gian hiện tại sau một đợi chờ hai giây. Đoạn mã bên dưới hiển thị mã khởi đầu cho cách FutureBuilder
có thể được sử dụng cho tình huống này.
import 'package:flutter/material.dart';
class FutureBuilderExample extends StatefulWidget {
const FutureBuilderExample({Key? key}) : super(key: key);
@override
State<FutureBuilderExample> createState() => _FutureBuilderExampleState();
}
class _FutureBuilderExampleState extends State<FutureBuilderExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Future Builder Example"),
),
body: Container(),
);
}
}
Đoạn mã tiếp theo cho thấy cách tạo hàm Future.
Future<String> fetchTime() async {
await Future.delayed(const Duration(seconds: 2));
var date = DateTime.now();
return "${date.hour}:${date.minute.toString().padLeft(2, '0')}:${(date.second).toString().padLeft(2, '0')}";
}
Hàm fetchTime()
được sử dụng trong đoạn mã trên sẽ trả về thời gian hiện tại theo cách bất đồng bộ.
- Vì đây là một hàm bất đồng bộ, từ khóa async và
Future<String>
được sử dụng, trong đóString
chỉ loại dữ liệu trả về. - Vì việc thực thi sẽ bị trì hoãn trong hai giây, phương thức Future.delayed được sử dụng.
- Từ khóa
await
được sử dụng trướcFuture.delayed
để thông báo cho hàm chờ cho đến khi thực thi hoàn tất. Do đó, hàm sẽ tiếp tục chỉ sau khi thực thi hoàn tất.
- Từ khóa
- Tiếp theo,
DateTime.now()
được sử dụng để lấy ngày và giờ hiện tại và được gán cho biếndate
. - Cuối cùng, giá trị của
date
được định dạng một cách đẹp mắt để người dùng có thể hiểu được. Bạn có thể định dạng nó theo bất kỳ cách nào bạn muốn.
Tiếp theo là FutureBuilder
:
Đoạn mã bên dưới cho thấy cách định nghĩa FutureBuilder
trong widget Center
body: Center(
child: FutureBuilder(
future: fetchTime(),
builder: (context, snapshot) {},
),
),
Trong mã, fetchTime()
trả về một String
và chỉ định điều đó trong widget như FutureBuilder<String>
.
Xem xét nhiều kịch bản khi xử lý dữ liệu bất đồng bộ. Đoạn mã bên dưới demonstrates kịch bản xử lý dữ liệu bất đồng bộ và Connect ionStates bên trong widget FutureBuilder
.
body: Center(
child: FutureBuilder<String>(
future: fetchTime(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Error',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
),
Text(
snapshot.error.toString(),
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
],
);
}
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Time Now',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
),
Text(
snapshot.data.toString(),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
],
);
}
return Text('state: ${snapshot.connectionState}');
},
),
),
Trong đoạn mã trên, bên trong phương thức builder
của widget FutureBuilder
,connectionState
được xác minh trước bằng snapshot.connectionState
ConnectionState.waiting
để kiểm tra xem phương thức fetchTime()
đã trả về kết quả hay vẫn đang tải. Nếu nó vẫn đang tải, thì một widget CircularProgressIndicator()
có thể được trả về để thông báo cho người dùng biết rằng dữ liệu đang được tải.
Tiếp theo, kiểm tra xem dữ liệu trả về có bất kỳ lỗi nào không bằng cách sử dụng snapshot.hasError
. Nếu có lỗi, sau đó hiển thị lỗi bằng cách sử dụng widget Text.
Nếu dữ liệu đã được tải và không có lỗi nào xảy ra, thì dữ liệu hiện có được xác minh bằng cách sử dụng snapshot.hasData
. Dữ liệu, là một chuỗi trong trường hợp này, có thể được lấy bằng cách sử dụng snapshot.data và được hiển thị bên trong một widget Text.
Theo mặc định, FutureBuilder
phải trả về một cái gì đó. Vì vậy, hãy sử dụng một số widget để điền vào không gian đó ngay cả khi tất cả các điều kiện có thể xảy ra đã được xử lý. Ở đây, một widget Text được sử dụng để trả về connectionState
.
Hình bên dưới mô tả kết quả đầu ra.
StreamBuilder trong Flutter
Các luồng (streams) tương tự như Future
. Các luồng cũng là dữ liệu bất đồng bộ, nhưng dưới dạng một chuỗi các sự kiện. Một Future sẽ kết thúc sau khi tạo ra kết quả. Trong khi đó, một luồng là một kết nối đã được thiết lập và dữ liệu sẽ được nhận khi luồng đang hoạt động.
Một ví dụ tốt là một ứng dụng trò chuyện nơi cả người gửi và người nhận sẽ nhận dữ liệu miễn là các thành viên đang hoạt động. Một số ví dụ khác bao gồm video streaming, phát nhạc và vị trí trực tiếp.
Widget StreamBuilder trong Flutter
StreamBuilder trong Flutter giúp lấy và hiển thị các luồng dữ liệu. Tương tự như FutureBuilder, thay vì đối số future, có một đối số stream.
Cú pháp cho StreamBuilder được đưa ra như sau:
Hàm Generator
Một hàm Generator cho phép người dùng tạo ra một giá trị trong chuỗi. Đơn giản, người dùng có thể tạo ra một tập hợp các đối tượng có thể được truy cập theo thứ tự. Hai từ khóa chính được sử dụng trong hàm Generator là async*
và yield
.
asynce*
: Luôn trả về một luồng và cung cấp một số cú pháp tiện lợi để phát ra một giá trị thông qua từ khóa yield.yield
: Thêm một giá trị vào luồng kết quả của hàm async* xung quanh. Nó giống như một hàm return nhưng không kết thúc hàm.
Để hiểu với một ví dụ, hãy xem xét đoạn mã bên dưới trong đó hàm fetchTimer()
là một đối tượng Timer sẽ đếm từ 10 xuống 0 và kết thúc.
int i = 10;
Stream<String> fetchTimer() async* {
while (i > 0) {
await Future.delayed(Duration(seconds: 1));
yield i.toString();
i--;
}
}
- Mã mẫu 5 có một hàm fetchTimer() sẽ đếm từ 10 xuống 0 và kết thúc.
- Một biến số nguyên được khởi tạo với giá trị 10 bên ngoài hàm
fetchTimer()
.
fetchTimer()
là một hàm luồng trả về một chuỗi các giá trị không đồng bộ. Do đó, các từ khóaasync*
vàStream<String>
được sử dụng, trong đóString
là kiểu trả về.
- Bên trong hàm, sử dụng vòng lặp while để kiểm tra xem biến
i
có lớn hơn không hay không. Nó tiếp tục lặp cho đến khi điều kiện trở thành sai.
- Bên trong vòng lặp, tạo một đợi hai giây bằng cách sử dụng phương thức
Future.delayed
và từ khóaasync
.
- Sau đó, giảm giá trị của
i
đi một.
- Cuối cùng, giá trị của
i
được trả về dưới dạngString
bằng từ khóayield
.
Tạo Ứng Dụng để Thể Hiện StreamBuilder trong Flutter
Sử dụng hàm generic
như một ví dụ ở đây.
Trong đó, tạo một ứng dụng hẹn giờ 10 giây, bắt đầu sau một giây và kết thúc sau 10 đếm giảm xuống 0. Sau đó, hiển thị một thông báo “Hết giờ”.
Đoạn mã cho thấy cách tạo một widget mới gọi là StreamBuilderExample
. Bắt đầu bằng cách tạo StreamBuilder
và bọc nó trong widget Center
.
body: Center(
child: StreamBuilder<String>(
stream: fetchTimer(),
builder: (context, snapshot) {
// something
},
),
),
Hàm fetchTimer()
được khai báo là luồng. Sau đó, bắt đầu xây dựng UI.
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Lỗi',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
),
Text(
snapshot.error.toString(),
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
],
);
}
if (snapshot.connectionState == ConnectionState.active) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Hết giờ trong:',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
),
Text(
snapshot.data.toString(),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
],
);
}
if (snapshot.connectionState == ConnectionState.done) {
return Text(
'Hết giờ! \n${snapshot.connectionState}',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 20,
),
);
}
return Container();
},
Khi thực thi mã, đầu tiên, kiểm tra xem dữ liệu có đang tải hay không. Sử dụng ConnectionState.waiting
để kiểm tra dữ liệu đang tải, và sử dụng CircularProgressIndicator()
để thông báo cho người dùng.
Tiếp theo, kiểm tra lỗi bằng cách sử dụng phương thức snapshot.hasError
bên trong một điều kiện if. Nếu có bất kỳ lỗi nào, hiển thị lỗi trong Text
widget. Sau đó, sử dụng các trạng thái kết nối khác và hiển thị dữ liệu theo từng trạng thái.
ConnectionState.active
có nghĩa là kết nối đã được thiết lập và dữ liệu đang được gửi đến. Hiển thị thời gian trong một Text
widget.
ConnectionState.done
có nghĩa là kết nối đã đóng và không còn nhận dữ liệu nữa. Hiển thị thông báo “Hết giờ”. Tham khảo hình bên dưới để xem kết quả của ứng dụng Thời gian được tạo bằng StreamBuilder
.
Sự khác biệt giữa FutureBuilder và StreamBuilder
Feature | FutureBuilder | StreamBuilder |
Sử dụng | FutureBuilder được sử dụng cho một lần trả về | StreamBuilder được sử dụng để lấy dữ liệu nhiều lần. Ví dụ như lắng nghe sự thay đổi dữ liệu |
Khi có dữ liệu | Khi FutureBuilder lấy kết quả, nó kết thúc. Widget sẽ không cập nhật nếu dữ liệu thay đổi và kết nối sẽ kết thúc | StreamBuilder sẽ tái tạo UI mỗi khi dữ liệu thay đổi cho đến khi kết nối còn hoạt động |
Từ khóa chính | Future, async | Stream, async*, yield |
Ví dụ sử dụng | Yêu cầu HTTP, tải file từ thiết bị, chụp ảnh từ camera, lấy thông tin thiết bị như vị trí và pin | Ví dụ như vị trí thời gian thực, phát video/nhạc, lắng nghe WebSocket |