+import 'package:mts/app/di/service_locator.dart';
+import 'package:mts/data/mappers/order_option_mapper.dart';
+import 'package:mts/domain/entities/order_option.dart';
+import 'package:mts/domain/repositories/order_option_repository.dart';
+import 'package:mts/domain/repositories/local/order_option_repository.dart' as local;
+
+/// Clean implementation of the OrderOptionRepository interface
+/// This implementation doesn't depend on notifiers
+class OrderOptionRepositoryCleanImpl implements OrderOptionRepository {
+ final local.LocalOrderOptionRepository _localRepository;
+
+ OrderOptionRepositoryCleanImpl({
+ local.LocalOrderOptionRepository? localRepository,
+ }) : _localRepository = localRepository ??
+ ServiceLocator.get<local.LocalOrderOptionRepository>();
+
+ @override
+ Future<List<OrderOption>> getOrderOptions() async {
+ // Get order options from the local repository
+ final models = await _localRepository.getListOrderOptionModel();
+
+ // Convert to domain entities
+ return OrderOptionMapper.fromModelList(models);
+ }
+
+ @override
+ Future<OrderOption?> getSelectedOrderOption() async {
+ // Get all order options
+ final orderOptions = await getOrderOptions();
+
+ // Return the first one as selected (or null if empty)
+ return orderOptions.isNotEmpty ? orderOptions.first : null;
+ }
+
+ @override
+ Future<void> setSelectedOrderOption(OrderOption orderOption) async {
+ // In a real implementation, you might store the selected option ID in preferences
+ // For now, we'll just ensure the option exists in the database
+ final model = OrderOptionMapper.toModel(orderOption);
+ await _localRepository.insertBulk([model]);
+ }
+}
+import 'package:mts/app/di/service_locator.dart';
+import 'package:mts/data/mappers/order_option_mapper.dart';
+import 'package:mts/domain/entities/order_option.dart';
+import 'package:mts/domain/repositories/sale_item_repository.dart';
+import 'package:mts/bloc/sale_item_bloc.dart';
+
+/// Clean implementation of the SaleItemRepository interface
+/// This implementation doesn't depend on notifiers
+class SaleItemRepositoryCleanImpl implements SaleItemRepository {
+ @override
+ Future<void> setSelectedCategory(OrderOption category) async {
+ // Convert domain entity to data model
+ final model = OrderOptionMapper.toModel(category);
+
+ // Use the existing bloc to set the category
+ // In a fully clean architecture, this would be replaced with a direct database call
+ await SaleItemBloc.setSelectedCategoryEat(model);
+ }
+}
Step 3: Register the Repositories in ServiceLocator
+import 'package:mts/app/di/service_locator.dart';
+import 'package:mts/data/repositories/order_option_repository_clean_impl.dart';
+import 'package:mts/data/repositories/sale_item_repository_clean_impl.dart';
+import 'package:mts/domain/repositories/order_option_repository.dart';
+import 'package:mts/domain/repositories/sale_item_repository.dart';
+import 'package:mts/domain/usecases/get_order_options_usecase.dart';
+import 'package:mts/domain/usecases/get_selected_order_option_usecase.dart';
+import 'package:mts/domain/usecases/select_order_option_usecase.dart';
+import 'package:mts/presentation/blocs/order_option/order_option_bloc.dart';
+
+/// Extension for ServiceLocator to register clean architecture components
+extension ServiceLocatorCleanExtension on ServiceLocator {
+ /// Register clean architecture components
+ static void registerCleanArchitectureComponents() {
+ // Register repositories
+ ServiceLocator.registerSingleton<OrderOptionRepository>(
+ OrderOptionRepositoryCleanImpl(),
+ );
+
+ ServiceLocator.registerSingleton<SaleItemRepository>(
+ SaleItemRepositoryCleanImpl(),
+ );
+
+ // Register use cases
+ ServiceLocator.registerSingleton<GetOrderOptionsUseCase>(
+ GetOrderOptionsUseCase(ServiceLocator.get<OrderOptionRepository>()),
+ );
+
+ ServiceLocator.registerSingleton<GetSelectedOrderOptionUseCase>(
+ GetSelectedOrderOptionUseCase(ServiceLocator.get<OrderOptionRepository>()),
+ );
+
+ ServiceLocator.registerSingleton<SelectOrderOptionUseCase>(
+ SelectOrderOptionUseCase(
+ ServiceLocator.get<OrderOptionRepository>(),
+ ServiceLocator.get<SaleItemRepository>(),
+ ),
+ );
+
+ // Register BLoC
+ ServiceLocator.registerSingleton<OrderOptionBloc>(
+ OrderOptionBloc(
+ getOrderOptionsUseCase: ServiceLocator.get<GetOrderOptionsUseCase>(),
+ getSelectedOrderOptionUseCase: ServiceLocator.get<GetSelectedOrderOptionUseCase>(),
+ selectOrderOptionUseCase: ServiceLocator.get<SelectOrderOptionUseCase>(),
+ ),
+ );
+ }
+}
Step 4: Update the ChooseCategory Widget to Use BLoC Directly
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:mts/app/di/service_locator.dart';
+import 'package:mts/domain/entities/order_option.dart';
+import 'package:mts/presentation/blocs/order_option/order_option_bloc.dart';
+import 'package:mts/presentation/common/widgets/styled_dropdown.dart';
+
+class ChooseCategoryClean extends StatefulWidget {
+ final Function() chooseCallback;
+
+ const ChooseCategoryClean({super.key, required this.chooseCallback});
+
+ @override
+ State<ChooseCategoryClean> createState() => _ChooseCategoryCleanState();
+}
+
+class _ChooseCategoryCleanState extends State<ChooseCategoryClean> {
+ late OrderOptionBloc _orderOptionBloc;
+
+ @override
+ void initState() {
+ super.initState();
+ // Get the bloc from ServiceLocator
+ _orderOptionBloc = ServiceLocator.get<OrderOptionBloc>();
+ // Load order options when the widget initializes
+ _orderOptionBloc.add(LoadOrderOptionsEvent());
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<OrderOptionBloc, OrderOptionState>(
+ bloc: _orderOptionBloc, // Use the bloc from ServiceLocator
+ builder: (context, state) {
+ if (state is OrderOptionLoading) {
+ return const Center(child: CircularProgressIndicator());
+ } else if (state is OrderOptionError) {
+ return Center(child: Text('Error: ${state.message}'));
+ } else if (state is OrderOptionsLoaded) {
+ return _buildDropdown(context, state);
+ } else {
+ return const SizedBox(); // Initial state or unknown state
+ }
+ },
+ );
+ }
+
+ Widget _buildDropdown(BuildContext context, OrderOptionsLoaded state) {
+ return StyledDropdown<OrderOption>(
+ isHaveBorder: false,
+ items: state.orderOptions.map<DropdownMenuItem<OrderOption>>((
+ OrderOption option,
+ ) {
+ return DropdownMenuItem<OrderOption>(
+ value: option,
+ child: Text(
+ option.name.toString(),
+ style: const TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ );
+ }).toList(),
+ selected: state.selectedOrderOption,
+ list: state.orderOptions,
+ setDropdownValue: (value) {
+ // Dispatch event to select the order option
+ _orderOptionBloc.add(SelectOrderOptionEvent(value));
+
+ // Call the callback
+ widget.chooseCallback();
+ },
+ );
+ }
+}
Step 5: Create a Main File to Initialize Clean Architecture
+import 'package:mts/app/di/service_locator.dart';
+import 'package:mts/app/di/service_locator_clean_extension.dart';
+
+/// Initializer for Clean Architecture components
+class CleanArchitectureInitializer {
+ /// Initialize all Clean Architecture components
+ static void initialize() {
+ // Register all Clean Architecture components
+ ServiceLocatorCleanExtension.registerCleanArchitectureComponents();
+ }
+}
Step 6: Usage Example in a Screen
+import 'package:flutter/material.dart';
+import 'package:mts/presentation/features/sales/components/choose_category_clean_direct.dart';
+
+class SalesScreenCleanExample extends StatelessWidget {
+ const SalesScreenCleanExample({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Sales'),
+ ),
+ body: Column(
+ children: [
+ // Other widgets...
+
+ // Use the clean architecture version of ChooseCategory
+ ChooseCategoryClean(
+ chooseCallback: () {
+ // Handle category selection
+ print('Category selected');
+ },
+ ),
+
+ // Other widgets...
+ ],
+ ),
+ );
+ }
+}
Explanation of the Clean Architecture Implementation
Now let me explain the key differences and improvements in this implementation:
1. Elimination of Notifiers
In the original implementation, we had:
// In OrderOptionBloc
static OrderOptionModel? getOrderOptionModel(BuildContext context) {
final listOrderOption = Provider.of<OrderOptionNotifier>(context, listen: false).getListOrderOption;
// ...
}
// In ChooseCategory widget
selectedOrderOption = OrderOptionBloc.getOrderOptionModel(context);
// ...
Provider.of<SaleItemNotifier>(context, listen: false).setSelectedCategoryEat(orderOptionModel);
In the new implementation:
// In OrderOptionBloc (proper BLoC pattern)
Future<void> _onLoadOrderOptions(LoadOrderOptionsEvent event, Emitter<OrderOptionState> emit) async {
// ...
final orderOptions = await _getOrderOptionsUseCase.execute();
final selectedOrderOption = await _getSelectedOrderOptionUseCase.execute();
emit(OrderOptionsLoaded(orderOptions: orderOptions, selectedOrderOption: selectedOrderOption));
}
// In ChooseCategoryClean widget
return BlocBuilder<OrderOptionBloc, OrderOptionState>(
bloc: _orderOptionBloc,
builder: (context, state) {
if (state is OrderOptionsLoaded) {
// Use state.orderOptions and state.selectedOrderOption
}
}
);
2. Integration with ServiceLocator
Instead of creating a separate dependency injection system, we're using your existing ServiceLocator:
// Get the bloc from ServiceLocator
_orderOptionBloc = ServiceLocator.get<OrderOptionBloc>();
3. Clean Repository Implementation
The repository implementations no longer depend on notifiers:
// Original implementation (with notifiers)
OrderOptionRepositoryImpl(this._notifier, this._context);
// Clean implementation
OrderOptionRepositoryCleanImpl({
local.LocalOrderOptionRepository? localRepository,
}) : _localRepository = localRepository ??
ServiceLocator.get<local.LocalOrderOptionRepository>();
4. Proper BLoC Pattern
We're using the proper BLoC pattern with events and states:
// Events
class LoadOrderOptionsEvent extends OrderOptionEvent {}
class SelectOrderOptionEvent extends OrderOptionEvent { /* ... */ }
// States
class OrderOptionInitial extends OrderOptionState {}
class OrderOptionLoading extends OrderOptionState {}
class OrderOptionsLoaded extends OrderOptionState { /* ... */ }
class OrderOptionError extends OrderOptionState { /* ... */ }
// BLoC
class OrderOptionBloc extends Bloc<OrderOptionEvent, OrderOptionState> {
// ...
on<LoadOrderOptionsEvent>(_onLoadOrderOptions);
on<SelectOrderOptionEvent>(_onSelectOrderOption);
}
Benefits of This Approach
Single Source of Truth: The BLoC is the single source of truth for state
Testability: Each component can be tested independently
Separation of Concerns: UI, business logic, and data access are clearly separated
Maintainability: Changes to one layer don't affect others
Scalability: Easy to add new features or modify existing ones
How to Use This in Your Project
Initialize Clean Architecture components in your app's startup:
void main() {
// Initialize ServiceLocator
ServiceLocator.init();
// Initialize Clean Architecture components
CleanArchitectureInitializer.initialize();
runApp(MyApp());
}
Use the clean architecture components in your widgets:
// Get the bloc from ServiceLocator
final orderOptionBloc = ServiceLocator.get<OrderOptionBloc>();
// Use it in your widget
BlocBuilder<OrderOptionBloc, OrderOptionState>(
bloc: orderOptionBloc,
builder: (context, state) {
// Build UI based on state
}
);
Gradual Migration Strategy
You don't have to convert your entire app at once. Here's a strategy for gradual migration:
Start with one feature (like OrderOption)
Create domain entities, use cases, and repositories for that feature
Create a BLoC for that feature
Update the UI to use the BLoC
Keep the existing notifiers for backward compatibility
Gradually migrate other features
Conclusion
This implementation provides a clean architecture approach that:
Eliminates the need for notifiers in favor of BLoCs
Integrates with your existing ServiceLocator
Follows proper separation of concerns
Is testable and maintainable
The key difference from the previous implementation is that we're no longer using notifiers at all - we're using proper BLoCs with events and states, and we're integrating with your existing ServiceLocator instead of creating a new dependency injection system.
Would you like me to explain any specific part of this implementation in more detail?