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

Что такое Provider и как он работает?

2.0 Middle🔥 191 комментариев
#State Management#Архитектура Flutter

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

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

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

Что такое Provider и как он работает?

Provider — это один из самых популярных пакетов для управления состоянием в Flutter. Он решает проблему передачи данных между виджетами и упрощает архитектуру приложения. Это современный и легкий подход к state management.

Что такое Provider?

Provider — это паттерн, который берет информацию из точки A и предоставляет (provides) её в точку B, без необходимости передавать через все промежуточные виджеты (prop drilling).

Без Provider (prop drilling):
Parent → Child1 → Child2 → Child3 → Child4
(передаем data через каждый слой)

С Provider (внедрение зависимостей):
Child1, Child2, Child3, Child4 все получают data
напрямую из глобального контекста

Типы Provider'ов

1. Provider — для неизменяемых данных

import 'package:flutter_riverpod/flutter_riverpod.dart';

final myValueProvider = Provider<String>((ref) {
  return 'Hello World';
});

// Использование
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(myValueProvider);
    return Text(value); // 'Hello World'
  }
}

2. StateProvider — для простого состояния

final counterProvider = StateProvider<int>((ref) => 0);

class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    
    return Column(
      children: [
        Text('Counter: $count'),
        ElevatedButton(
          onPressed: () {
            ref.read(counterProvider.notifier).state++;
          },
          child: Text('Increment'),
        ),
      ],
    );
  }
}

3. FutureProvider — для асинхронных данных

final userProvider = FutureProvider<User>((ref) async {
  final response = await http.get('/api/user/1');
  return User.fromJson(jsonDecode(response.body));
});

class UserWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    
    return userAsync.when(
      data: (user) => Text('User: ${user.name}'),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

4. StreamProvider — для потоков данных

final clockProvider = StreamProvider<DateTime>((ref) async* {
  while (true) {
    yield DateTime.now();
    await Future.delayed(Duration(seconds: 1));
  }
});

class ClockWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final timeAsync = ref.watch(clockProvider);
    
    return timeAsync.when(
      data: (time) => Text('Time: ${time.hour}:${time.minute}'),
      loading: () => Text('Loading...'),
      error: (err, stack) => Text('Error'),
    );
  }
}

Основные концепции

watch() vs read()

class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // watch — слушает изменения и перестраивает виджет
    final value = ref.watch(myProvider);
    // Если myProvider изменится, build() вызовется заново
    
    return ElevatedButton(
      onPressed: () {
        // read — получает текущее значение БЕЗ слушания
        final value = ref.read(myProvider);
        // read используется в обработчиках событий
      },
      child: Text('Press'),
    );
  }
}

Invalidate — для инвалидации кеша

final userProvider = FutureProvider<User>((ref) async {
  return fetchUser();
});

class RefreshButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        // Инвалидируем провайдер
        ref.invalidate(userProvider);
        // Он перезагружает данные
      },
      child: Text('Refresh'),
    );
  }
}

Зависимости между Provider'ами

final userIdProvider = StateProvider<String>((ref) => '1');

final userProvider = FutureProvider<User>((ref) {
  final userId = ref.watch(userIdProvider);
  return fetchUser(userId); // автоматически перезагружается при изменении userId
});

final userNameProvider = Provider<String>((ref) {
  final user = ref.watch(userProvider).value;
  return user?.name ?? 'Unknown';
});

// Использование
class UserProfileWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userName = ref.watch(userNameProvider);
    final userId = ref.watch(userIdProvider);
    
    return Column(
      children: [
        Text('ID: $userId'),
        Text('Name: $userName'),
        ElevatedButton(
          onPressed: () {
            ref.read(userIdProvider.notifier).state = '2';
            // userProvider и userNameProvider автоматически обновятся
          },
          child: Text('Change User'),
        ),
      ],
    );
  }
}

Практический пример: TodoList приложение

// Модель
class Todo {
  final String id;
  final String title;
  final bool completed;
  
  Todo({required this.id, required this.title, this.completed = false});
  
  Todo copyWith({String? title, bool? completed}) {
    return Todo(
      id: id,
      title: title ?? this.title,
      completed: completed ?? this.completed,
    );
  }
}

// Provider для списка todos
final todosProvider = StateProvider<List<Todo>>((ref) => [
  Todo(id: '1', title: 'Learn Flutter'),
  Todo(id: '2', title: 'Build App'),
]);

// Provider для выбранного фильтра
final filterProvider = StateProvider<String>((ref) => 'all'); // all, completed, active

// Provider для отфильтрованных todos
final filteredTodosProvider = Provider<List<Todo>>((ref) {
  final todos = ref.watch(todosProvider);
  final filter = ref.watch(filterProvider);
  
  switch (filter) {
    case 'completed':
      return todos.where((t) => t.completed).toList();
    case 'active':
      return todos.where((t) => !t.completed).toList();
    default:
      return todos;
  }
});

// Provider для количества завершенных
final completedCountProvider = Provider<int>((ref) {
  final todos = ref.watch(todosProvider);
  return todos.where((t) => t.completed).length;
});

// Widget для отображения
class TodoListWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(filteredTodosProvider);
    final completedCount = ref.watch(completedCountProvider);
    final filter = ref.watch(filterProvider);
    
    return Column(
      children: [
        // Фильтры
        Row(
          children: ['all', 'active', 'completed'].map((f) {
            return ElevatedButton(
              onPressed: () {
                ref.read(filterProvider.notifier).state = f;
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: filter == f ? Colors.blue : Colors.grey,
              ),
              child: Text(f),
            );
          }).toList(),
        ),
        // Статистика
        Text('Completed: $completedCount'),
        // Список
        Expanded(
          child: ListView.builder(
            itemCount: todos.length,
            itemBuilder: (context, index) {
              final todo = todos[index];
              return ListTile(
                title: Text(todo.title),
                leading: Checkbox(
                  value: todo.completed,
                  onChanged: (_) {
                    final todosNotifier = ref.read(todosProvider.notifier);
                    final newTodos = ref.read(todosProvider);
                    newTodos[newTodos.indexOf(todo)] = todo.copyWith(
                      completed: !todo.completed,
                    );
                    todosNotifier.state = [...newTodos];
                  },
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

Сравнение подходов

Без Provider (prop drilling):

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int counter = 0;
  
  @override
  Widget build(BuildContext context) {
    return HomeScreen(counter: counter, onIncrement: () {
      setState(() => counter++);
    });
  }
}

class HomeScreen extends StatelessWidget {
  final int counter;
  final Function onIncrement;
  
  HomeScreen({required this.counter, required this.onIncrement});
  
  @override
  Widget build(BuildContext context) {
    return CounterDisplay(
      counter: counter,
      onIncrement: onIncrement,
    );
  }
}

class CounterDisplay extends StatelessWidget {
  // Много prop drilling!
}

С Provider (чисто):

final counterProvider = StateProvider<int>((ref) => 0);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: HomeScreen(),
    );
  }
}

class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    return CounterDisplay(counter: counter);
  }
}

class CounterDisplay extends ConsumerWidget {
  final int counter;
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () {
        ref.read(counterProvider.notifier).state++;
      },
      child: Text('$counter'),
    );
  }
}

Преимущества Provider

Простота — легко начать ✅ Мощность — поддерживает сложные сценарии ✅ Производительность — отстраивает только нужные виджеты ✅ Тестируемость — легко мокировать ✅ Документация — отличные гайды

Когда использовать Provider

  • Управление состоянием приложения
  • Обмен данными между несвязанными виджетами
  • Кеширование асинхронных данных
  • Управление зависимостями
  • Практически в каждом реальном приложении

Вывод

Provider (и его современная версия Riverpod) — это мощный и удобный способ управления состоянием в Flutter. Он:

  • Избавляет от prop drilling
  • Обеспечивает реактивность
  • Поддерживает асинхронные данные
  • Легко тестируется

Это один из must-know навыков для каждого Flutter разработчика.