← Назад к вопросам

Приведи пример использования Safe Area

1.0 Junior🔥 251 комментариев
#Flutter виджеты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример использования FutureBuilder

FutureBuilder — это один из самых полезных виджетов в Flutter для работы с асинхронными операциями.

Основной пример: загрузка данных пользователя

import 'package:flutter/material.dart';

class UserProfilePage extends StatelessWidget {
  final String userId;
  
  UserProfilePage({required this.userId});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User Profile')),
      body: FutureBuilder<User>(
        future: fetchUser(userId),
        builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
          // Проверяем состояние
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            return UserContent(user: snapshot.data!);
          } else {
            return Center(child: Text('No data'));
          }
        },
      ),
    );
  }
}

class UserContent extends StatelessWidget {
  final User user;
  
  UserContent({required this.user});
  
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          CircleAvatar(radius: 50, backgroundImage: NetworkImage(user.avatar)),
          SizedBox(height: 20),
          Text(user.name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
          SizedBox(height: 10),
          Text(user.email),
          SizedBox(height: 20),
          Text('Bio: ${user.bio}'),
        ],
      ),
    );
  }
}

future<User> fetchUser(String userId) async {
  final response = await http.get(Uri.parse('https://api.example.com/users/$userId'));
  if (response.statusCode == 200) {
    return User.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to load user');
  }
}

class User {
  final String id;
  final String name;
  final String email;
  final String avatar;
  final String bio;
  
  User({
    required this.id,
    required this.name,
    required this.email,
    required this.avatar,
    required this.bio,
  });
  
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      avatar: json['avatar'],
      bio: json['bio'],
    );
  }
}

Состояния FutureBuilder

FutureBuilder имеет 4 основных состояния:

  1. ConnectionState.none — Future еще не создан
  2. ConnectionState.waiting — Future выполняется (загрузка)
  3. ConnectionState.active — Stream активен (не для Future)
  4. ConnectionState.done — Future завершен

Пример с эффектной загрузкой

class DataListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Posts')),
      body: FutureBuilder<List<Post>>(
        future: fetchPosts(),
        builder: (context, snapshot) {
          // Состояние загрузки
          if (snapshot.connectionState == ConnectionState.waiting) {
            return _LoadingWidget();
          }
          
          // Состояние ошибки
          if (snapshot.hasError) {
            return _ErrorWidget(
              error: snapshot.error.toString(),
              onRetry: () {
                // Пересчитать Future
              },
            );
          }
          
          // Нет данных
          if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return _EmptyWidget();
          }
          
          // Успешная загрузка
          final posts = snapshot.data!;
          return ListView.builder(
            itemCount: posts.length,
            itemBuilder: (context, index) {
              return PostCard(post: posts[index]);
            },
          );
        },
      ),
    );
  }
}

class _LoadingWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          CircularProgressIndicator(),
          SizedBox(height: 16),
          Text('Loading posts...'),
        ],
      ),
    );
  }
}

class _ErrorWidget extends StatelessWidget {
  final String error;
  final VoidCallback onRetry;
  
  _ErrorWidget({required this.error, required this.onRetry});
  
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.error_outline, size: 64, color: Colors.red),
          SizedBox(height: 16),
          Text('Error: $error'),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: onRetry,
            child: Text('Retry'),
          ),
        ],
      ),
    );
  }
}

class _EmptyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.inbox, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text('No posts found'),
        ],
      ),
    );
  }
}

Best Practice: Использование с Riverpod

final postsProvider = FutureProvider<List<Post>>((ref) async {
  return fetchPosts();
});

class PostsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final posts = ref.watch(postsProvider);
    
    return posts.when(
      data: (posts) => ListView.builder(
        itemCount: posts.length,
        itemBuilder: (context, index) => PostCard(post: posts[index]),
      ),
      loading: () => Center(child: CircularProgressIndicator()),
      error: (error, st) => Center(child: Text('Error: $error')),
    );
  }
}

Важные моменты

  1. Future кэшируется — Future не пересчитывается, пока виджет не пересоздан
  2. Обработка null данных — используйте .data! с осторожностью
  3. Отмена запросов — FutureBuilder не автоматически отменяет Future
  4. Performance — для часто обновляемых данных лучше использовать StreamBuilder
Приведи пример использования Safe Area | PrepBro