← Назад к вопросам
Как следить за изменниями компонента в MVVM?
1.0 Junior🔥 11 комментариев
#Архитектура Flutter
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как следить за изменениями компонента в MVVM?
MVVM (Model-View-ViewModel) в Flutter строится на принципе реактивности, где View (UI) автоматически обновляется при изменении данных в ViewModel. Существует несколько подходов для отслеживания изменений.
1. StreamBuilder (Stream + ViewModel)
Один из самых распространённых подходов — использование Stream для отслеживания изменений:
class CounterViewModel {
int _count = 0;
final _countController = StreamController<int>();
Stream<int> get countStream => _countController.stream;
void increment() {
_count++;
_countController.sink.add(_count);
}
void dispose() {
_countController.close();
}
}
class CounterView extends StatefulWidget {
@override
State<CounterView> createState() => _CounterViewState();
}
class _CounterViewState extends State<CounterView> {
late CounterViewModel viewModel = CounterViewModel();
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: viewModel.countStream,
initialData: 0,
builder: (context, snapshot) {
return Column(
children: [
Text("Счёт: ${snapshot.data}"),
ElevatedButton(
onPressed: viewModel.increment,
child: Text("Увеличить"),
),
],
);
},
);
}
@override
void dispose() {
viewModel.dispose();
super.dispose();
}
}
2. ValueNotifier + ValueListenableBuilder
Для простых случаев можно использовать ValueNotifier:
class UserViewModel {
final userName = ValueNotifier<String>("Иван");
final userAge = ValueNotifier<int>(25);
void updateName(String newName) {
userName.value = newName;
}
void updateAge(int newAge) {
userAge.value = newAge;
}
void dispose() {
userName.dispose();
userAge.dispose();
}
}
class UserView extends StatefulWidget {
@override
State<UserView> createState() => _UserViewState();
}
class _UserViewState extends State<UserView> {
late UserViewModel viewModel = UserViewModel();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<String>(
valueListenable: viewModel.userName,
builder: (context, name, _) {
return ValueListenableBuilder<int>(
valueListenable: viewModel.userAge,
builder: (context, age, _) {
return Column(
children: [
Text("Имя: $name"),
Text("Возраст: $age"),
],
);
},
);
},
);
}
@override
void dispose() {
viewModel.dispose();
super.dispose();
}
}
3. BLoC паттерн (современный стандарт)
BLoC (Business Logic Component) — это паттерн, специально разработанный для управления состоянием:
class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterState {
final int count;
CounterState({required this.count});
}
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(count: 0)) {
on<IncrementEvent>((event, emit) {
emit(CounterState(count: state.count + 1));
});
on<DecrementEvent>((event, emit) {
emit(CounterState(count: state.count - 1));
});
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Column(
children: [
Text("Счёт: ${state.count}"),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
child: Text("Увеличить"),
),
],
);
},
);
}
}
4. Provider (фреймворк для управления состоянием)
Provider — это популярная библиотека для MVVM в Flutter:
class CounterViewModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Уведомляет об изменении
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterViewModel>(
builder: (context, viewModel, _) {
return Column(
children: [
Text("Счёт: ${viewModel.count}"),
ElevatedButton(
onPressed: viewModel.increment,
child: Text("Увеличить"),
),
],
);
},
);
}
}
5. Getx (простой и мощный)
Getx упрощает реактивность:
class CounterViewModel extends GetxController {
var count = 0.obs; // Observable переменная
void increment() {
count++;
}
}
class CounterView extends StatelessWidget {
final viewModel = Get.put(CounterViewModel());
@override
Widget build(BuildContext context) {
return Obx(() => Column(
children: [
Text("Счёт: ${viewModel.count}"),
ElevatedButton(
onPressed: viewModel.increment,
child: Text("Увеличить"),
),
],
));
}
}
Сравнение подходов
| Подход | Сложность | Производительность | Подходит для |
|---|---|---|---|
| StreamBuilder | Средняя | Хорошая | Простые приложения |
| ValueNotifier | Низкая | Отличная | Одного значения |
| BLoC | Высокая | Отличная | Больших приложений |
| Provider | Средняя | Хорошая | Среднего размера приложений |
| GetX | Низкая | Хорошая | Быстрой разработки |
Лучшие практики
- Всегда вызывайте dispose() для очистки ресурсов
- Используйте const конструкторы для Widget, чтобы избежать лишних перестроек
- Разделяйте логику и представление — ViewModel содержит логику, View содержит только UI
- Тестируйте ViewModel отдельно от UI
Выбор подхода зависит от сложности приложения: от простого StreamBuilder для учебных проектов до BLoC для production-приложений.