← Назад к вопросам
Как использовать FutureBuilder и StreamBuilder?
2.3 Middle🔥 162 комментариев
#Flutter виджеты#Асинхронность
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
FutureBuilder и StreamBuilder
Это два основных виджета для работы с асинхронными данными во Flutter. Понимание различий — ключ к правильной архитектуре.
FutureBuilder — для одноразовых операций
Используется когда результат получается один раз.
FutureBuilder<User>(
future: fetchUser(userId), // Future завершится один раз
builder: (context, snapshot) {
// snapshot содержит состояние Future
// 1. Проверяем состояние загрузки
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
// 2. Проверяем ошибки
if (snapshot.hasError) {
return Center(child: Text('Ошибка: ${snapshot.error}'));
}
// 3. Проверяем наличие данных
if (!snapshot.hasData) {
return Center(child: Text('Нет данных'));
}
// 4. Используем данные
final user = snapshot.data!;
return UserProfile(user: user);
},
)
StreamBuilder — для потока данных
Используется когда данные приходят постоянно.
StreamBuilder<List<Message>>(
stream: messagesStream, // Stream будет эмитировать множество значений
initialData: [], // Начальное значение
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Ошибка: ${snapshot.error}'));
}
final messages = snapshot.data ?? [];
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
return MessageTile(message: messages[index]);
},
);
},
)
Жизненный цикл ConnectionState
enum ConnectionState {
none, // Stream ещё не подписан
waiting, // Ждём первого результата
active, // Stream активный, данные могут приходить
done, // Stream завершён (только для Stream)
}
// Пример проверки
if (snapshot.connectionState == ConnectionState.none) {
return Text('Ещё не подключено');
} else if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active) {
return Text('Данные: ${snapshot.data}');
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('Завершено: ${snapshot.data}');
}
Практический пример: REST API
class UserRepository {
Future<User> fetchUser(String id) async {
final response = await http.get(Uri.parse('https://api.example.com/users/$id'));
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Ошибка загрузки пользователя');
}
}
}
class UserScreen extends StatelessWidget {
final String userId;
final repository = UserRepository();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Пользователь')),
body: FutureBuilder<User>(
future: repository.fetchUser(userId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Ошибка: ${snapshot.error}'));
}
final user = snapshot.data!;
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
CircleAvatar(radius: 50, backgroundImage: NetworkImage(user.avatar)),
SizedBox(height: 16),
Text(user.name, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
Text(user.email),
],
),
);
},
),
);
}
}
Практический пример: Real-time данные
class ChatRepository {
Stream<List<Message>> getMessages(String chatId) {
return FirebaseFirestore.instance
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timestamp')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => Message.fromJson(doc.data()))
.toList());
}
}
class ChatScreen extends StatelessWidget {
final String chatId;
final repository = ChatRepository();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Чат')),
body: StreamBuilder<List<Message>>(
stream: repository.getMessages(chatId),
initialData: [],
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Ошибка: ${snapshot.error}'));
}
final messages = snapshot.data ?? [];
return ListView.builder(
reverse: true, // Новые сообщения внизу
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[messages.length - 1 - index];
return MessageBubble(message: message);
},
);
},
),
);
}
}
Сравнение
| Аспект | FutureBuilder | StreamBuilder |
|---|---|---|
| Использование | Один результат | Множество результатов |
| Примеры | API запрос, загрузка файла | Реал-тайм чат, live-данные |
| Завершение | Всегда завершается | Может бежать бесконечно |
| connectionState | waiting → active/error/done | waiting → active → done |
| Производительность | Лучше для простых случаев | Оптимально для потоков |
Лучшие практики
1. Кэширование Future
class DataNotifier extends ChangeNotifier {
Future<Data>? _cachedFuture;
Future<Data> getData() {
return _cachedFuture ??= _loadData();
}
Future<Data> _loadData() async {
final response = await http.get(Uri.parse('...'));
return Data.fromJson(jsonDecode(response.body));
}
}
2. Отписка от Stream
class LiveDataWidget extends StatefulWidget {
@override
State<LiveDataWidget> createState() => _LiveDataWidgetState();
}
class _LiveDataWidgetState extends State<LiveDataWidget> {
late StreamSubscription subscription;
@override
void initState() {
super.initState();
// Если нужна явная подписка
subscription = dataStream.listen((data) {
print('Получены данные: $data');
});
}
@override
void dispose() {
subscription.cancel(); // Всегда отписываться!
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<Data>(
stream: dataStream,
builder: (context, snapshot) => ...,
);
}
}
3. Избегать множественных подписок
// ❌ Плохо — перестраивается часто
StreamBuilder(
stream: repository.getData(), // Новый Stream каждый раз!
builder: ...,
)
// ✅ Хорошо — один Stream
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final stream = repository.getData(); // Один Stream
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: stream,
builder: ...,
);
}
}