A Flutter application that demonstrates clean architecture with BLoC state management for selecting countries and their corresponding states.
- Country selection from a dynamic list fetched from API
- State selection based on the selected country
- Clean architecture implementation with separation of concerns
- BLoC pattern for state management
- Comprehensive unit and widget testing
- Error handling and loading states
This project follows Clean Architecture principles with the following structure:
lib/
├── core/ # Core functionality shared across features
│ ├── constants/ # API constants and configuration
│ ├── errors/ # Error handling (Failures and Exceptions)
│ └── utils/ # Utility functions
├── features/
│ └── location/ # Location selection feature
│ ├── data/ # Data layer
│ │ ├── datasources/ # Remote data sources for API calls
│ │ ├── models/ # Data models with JSON serialization
│ │ └── repositories/ # Repository implementations
│ ├── domain/ # Domain layer (business logic)
│ │ ├── entities/ # Business entities
│ │ ├── repositories/ # Repository contracts
│ │ └── usecases/ # Business use cases
│ └── presentation/ # Presentation layer
│ ├── bloc/ # BLoC state management
│ ├── screens/ # UI screens
│ └── widgets/ # Reusable widgets
└── injection/ # Dependency injection setup
- Responsibility: UI and user interaction
- Components:
- BLoC (Business Logic Component) for state management
- Screens and widgets for UI
- Dependencies: Only depends on Domain layer
- Responsibility: Business logic and rules
- Components:
- Entities: Core business objects
- Use Cases: Application-specific business rules
- Repository Interfaces: Contracts for data operations
- Dependencies: No dependencies on other layers (pure Dart)
- Responsibility: Data retrieval and caching
- Components:
- Models: Data transfer objects with JSON serialization
- Data Sources: Remote API implementations
- Repository Implementations: Concrete implementations of domain contracts
- Dependencies: Depends on Domain layer
The app uses BLoC (Business Logic Component) pattern for state management:
- Events: User actions (LoadCountriesEvent, SelectCountryEvent, SelectStateEvent)
- States: UI states (LocationInitial, LocationLoading, LocationLoaded, LocationError)
- BLoC: Transforms events into states through business logic
Uses GetIt for dependency injection to:
- Maintain loose coupling between layers
- Enable easy testing with mock implementations
- Centralize dependency configuration
The app integrates with a REST API:
- Base URL:
https://68a2b00ac5a31eb7bb1d7ad2.mockapi.io/api/v1 - Endpoints:
GET /countries- Fetches list of countriesGET /countries/{countryId}/states- Fetches states for a specific country
- Models: JSON serialization/deserialization
- Data Sources: API call behavior and error handling
- Repositories: Data flow and error transformation
- Use Cases: Business logic execution
- BLoC: Event to state transformations
- UI component rendering
- User interaction handling
- State-based UI updates
flutter test- Flutter SDK ^3.7.0
- Dart SDK ^3.7.0
- Clone the repository:
git clone https://github.com/[username]/flutter_coding_challenge.git
cd flutter_coding_challenge- Install dependencies:
flutter pub get- Run the app:
flutter runflutter analyzedart format lib/- flutter_bloc: State management
- bloc: Core BLoC library
- equatable: Value equality for entities and states
- http: HTTP client for API calls
- get_it: Service locator for dependency injection
- dartz: Functional programming utilities (Either type for error handling)
- mocktail: Mocking library for tests
- bloc_test: Testing utilities for BLoC
Rationale: Provides separation of concerns, making the code more maintainable, testable, and scalable. Each layer has a single responsibility and clear boundaries.
Rationale: Offers predictable state management with clear separation between business logic and UI. Makes testing easier and provides a reactive programming model.
Rationale: Abstracts data sources from business logic, allowing easy switching between different data sources (API, local database, cache) without affecting domain layer.
Rationale: Using dartz's Either type provides functional error handling without exceptions in the domain layer, making error cases explicit and type-safe.
Rationale: Promotes loose coupling, enables easy testing with mocks, and centralizes object creation and lifecycle management.