Что такое Provider и как он работает?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое 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 разработчика.