Ответ
Да, эти паттерны отлично сочетаются, так как решают разные задачи. В моих проектах я часто комбинирую их:
- BLoC управляет состоянием и бизнес-логикой
- ViewModel (в контексте MVVM) выступает как адаптер между BLoC и View
Реализация на примере аутентификации:
// 1. BLoC для бизнес-логики
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository _repository;
AuthBloc(this._repository) : super(AuthInitial()) {
on<LoginRequested>((event, emit) async {
emit(AuthLoading());
try {
final user = await _repository.login(event.email, event.password);
emit(AuthAuthenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
}
});
}
}
// 2. ViewModel для подготовки данных для UI
class LoginViewModel {
final AuthBloc _authBloc;
LoginViewModel(this._authBloc);
// Преобразуем State в удобные для View свойства
bool get isLoading => _authBloc.state is AuthLoading;
String? get error => _authBloc.state is AuthError
? (_authBloc.state as AuthError).message
: null;
bool get isAuthenticated => _authBloc.state is AuthAuthenticated;
// Методы, которые View вызывает через ViewModel
void login(String email, String password) {
_authBloc.add(LoginRequested(email, password));
}
}
// 3. View (Widget)
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AuthBloc(context.read<AuthRepository>()),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
final viewModel = LoginViewModel(context.read<AuthBloc>());
return Scaffold(
body: viewModel.isLoading
? CircularProgressIndicator()
: Column(
children: [
if (viewModel.error != null)
Text(viewModel.error!, style: TextStyle(color: Colors.red)),
LoginForm(
onLogin: viewModel.login,
),
],
),
);
},
),
);
}
}
Преимущества такого подхода:
-
Четкое разделение ответственности:
- BLoC: чистая бизнес-логика
- ViewModel: подготовка данных для отображения
- View: только отрисовка UI
-
Тестируемость: можно тестировать BLoC и ViewModel отдельно
-
Гибкость: легко заменить BLoC на другой state management (Provider, Riverpod)
-
Поддержка сложных сценариев: ViewModel может агрегировать данные из нескольких BLoC
Когда это избыточно:
- Для простых экранов (форма с 2 полями) используйте только StatefulWidget или Provider
- В небольших приложениях BLoC или ViewModel по отдельности будет достаточно
В крупном e-commerce приложении мы использовали эту комбинацию: BLoC работал с API и кэшем, а ViewModel подготавливал данные для сложных списков товаров с фильтрацией и сортировкой.