y_infra
A reusable Flutter infrastructure package that provides the building blocks for clean architecture Flutter applications.
Installation
dependencies:
y_infra:
path: ../y_infra # or from pub.dev / git
import 'package:y_infra/y_infra.dart';
Package Structure
lib/
├── core/ Cache, errors, logging, notifier, storage, theme
├── data/ Database (SQLite), file (CSV/JSON), network (Dio)
├── domain/ Repository with queue/dedup and cache invalidation
├── auth/ Token storage, AuthCubit, auth state management
├── firebase/ Analytics, messaging, realtime database
├── push/ Push notifications (Awesome Notifications)
├── platform/ Permission handlers, connectivity, location
├── base_features/ CRUD, list, paginated, operation cubits
├── components/ Reusable UI components (bottom sheet selector)
├── mixins/ SnackBarY, FilterableMixin
└── utils/ Formatters, generators, validators, routing, map
Core
Cache
In-memory cache with type-safe storage, TTL support, multiple strategies, and cache invalidation.
final cache = DefaultCache();
cache.set('users', userList, type: CacheType.personal, ttl: Duration(minutes: 10));
final users = cache.get<List<User>>('users');
cache.removeByPrefix('user_');
cache.removeAllType(CacheType.personal);
Variants: ReadOnlyCache, WriteOnlyCache, NoCache
Errors
Categorized error system with automatic Dio exception mapping.
try {
await dio.get('/endpoint');
} catch (e) {
final error = ErrorMapper.map(e);
print(error.category); // ErrorCategory.network
print(error.isRetryable); // true
print(error.shouldShowToUser); // true
}
Error types: NetworkError, TimeoutError, ServerError, UnauthorisedError, ValidationError, NotFoundError, ConflictError, RateLimitError, UnexpectedError
Storage
Unified ILocalStorage interface with SecureStorage and SharedPreferencesStorage implementations.
final storage = SecureStorage(FlutterSecureStorage());
await storage.save<String>('key', 'value');
await storage.save<Map>('data', {'nested': true});
final value = await storage.get<String>('key');
Logging
Structured logging with configurable printers and log levels.
final logger = DebugLogger(printer: ConsolePrinter());
logger.info('User logged in', data: LogData(tag: 'AUTH'));
logger.error('Failed to fetch', data: LogData(tag: 'API'));
Notifier
Stream-based event bus for reactive communication between layers.
final notifier = NotifierService();
notifier.listen('user_updated').listen((event) => print(event.data));
notifier.notify(key: 'user_updated', data: updatedUser);
Theme
ThemeExtension for custom colors used across the package.
MaterialApp(
theme: ThemeData(
extensions: [
YInfraColors(successColor: Colors.green, warningColor: Colors.orange),
],
),
)
Data
Network
Configurable Dio setup with a composable interceptor pipeline.
final config = BaseNetworkConfig(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 15),
);
final dio = config.createDio();
InterceptorPipeline
A composable interceptor that runs a list of interceptors in sequence. If any interceptor resolves or rejects, the rest are skipped.
dio.interceptors.add(
InterceptorPipeline([
AuthInterceptor(
authTokenStorage: tokenStorage,
onTokenRefresh: (refresh) => api.refreshToken(refresh),
onAuthFailure: () => authCubit.logout(),
retryDio: Dio(BaseOptions(baseUrl: 'https://api.example.com')),
skipAuthPaths: ['/login', '/register'],
),
LoggingInterceptor(),
]),
);
Interceptors run in declaration order:
onRequest: 1st → 2nd → 3rd → Dio sends the requestonResponse: 1st → 2nd → 3rd → response returnedonError: 1st → 2nd → 3rd → error returned
Any interceptor can short-circuit the pipeline:
handler.resolve(response)— skip remaining interceptors, return the responsehandler.reject(error)— skip remaining interceptors, return the errorhandler.next(...)— continue to the next interceptor
Each interceptor also works standalone without InterceptorPipeline:
dio.interceptors.addAll([
AuthInterceptor(...),
LoggingInterceptor(),
]);
Remote Datasource
Base class for API calls with authenticated request support.
class UserRemoteDatasource extends IRemoteDatasource {
UserRemoteDatasource(super.authTokenStorage, super.dio);
Future<User> getUser(int id) => doRequest(
() async {
final response = await dio.get('/users/$id');
return User.fromJson(response.data);
},
);
}
Database
SQLite abstraction with command builders and data mappers.
class UserTable extends IDatabaseTable {
const UserTable() : super('users');
@override
Map<String, String> get columns => {
'id': 'INTEGER PRIMARY KEY',
'name': 'TEXT',
'email': 'TEXT',
};
}
File
CSV and JSON file controllers with path providers.
final csv = CsvFileController();
final data = await csv.load('assets/data.csv');
await csv.save(DownloadPathProvider('export.csv'), converter);
Domain
Repository
Abstract repository with request deduplication, caching, and cache invalidation.
class UserRepository extends IRepository {
Future<User> getUser(int id) => queue(
'user_$id',
() => api.fetchUser(id),
cache: defaultCache,
cacheType: CacheType.personal,
invalidatePrefix: 'user_list',
);
}
Auth
Token storage, state management, and automatic auth failure handling.
Token Storage
final tokenStorage = AuthTokenStorage(secureStorage);
await tokenStorage.saveTokenPair(TokenPair(
accessToken: 'abc',
refreshToken: 'xyz',
));
final pair = await tokenStorage.getTokenPair();
AuthCubit
Base auth cubit that manages authentication state. Does NOT handle login — that's project-specific. Call onAuthenticated after a successful login.
// Setup
final authCubit = AuthCubit(
tokenStorage: tokenStorage,
notifier: notifierService, // optional: listens for auth failures
authFailureKey: 'auth_failure', // key that AuthInterceptor notifies on
);
// On app startup
await authCubit.checkAuth();
// After successful login
final tokens = await api.login(email, password);
authCubit.onAuthenticated(tokens);
// Logout
await authCubit.logout();
// Listen to state changes
BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) => switch (state) {
Authenticated() => HomePage(),
Unauthenticated(reason: final reason) => LoginPage(reason: reason),
AuthLoading() => SplashScreen(),
_ => SplashScreen(),
},
)
Auth states are base class — extend them in your project:
class AuthenticatedWithUser extends Authenticated {
final User user;
const AuthenticatedWithUser(this.user);
}
Base Features
Cubit-based state management patterns for common operations.
Operation
Single async operation with automatic state management and duplicate call guard.
class DeleteItemCubit extends BaseOperationCubit<void> {
final ItemRepository _repo;
DeleteItemCubit(this._repo);
Future<void> delete(int id) => execute(
targetId: id,
operation: () => _repo.delete(id),
);
}
States: OperationInitial → OperationInProgress → OperationSuccess<T> / OperationFailure
List
class StoresCubit extends BaseListCubit<Store> {
final StoreRepository _repo;
StoresCubit(this._repo);
@override
Future<List<Store>> fetchItems() => _repo.getStores();
}
Paginated
class ProductsCubit extends PaginatedCubit<Product> {
final ProductRepository _repo;
ProductsCubit(this._repo);
@override
int getId(Product item) => item.id;
@override
Future<PaginatedResponse<Product>> fetchPage(int page, int pageSize) =>
_repo.getProducts(page: page, pageSize: pageSize);
}
CRUD
Full create/read/update/delete with filtering and selection.
class UsersCubit extends CrudCubit<User> {
final UserRepository _repo;
UsersCubit(this._repo);
@override
int getId(User item) => item.id;
@override
Future<List<User>> fetchItems() => _repo.getUsers();
Future<void> createUser(CreateUserDto dto) => performSave(
operation: () => _repo.create(dto),
successMessage: 'User created',
updateList: (user) => addToList(user),
);
Future<void> deleteUser(int id) => performDelete(
operation: () => _repo.delete(id),
id: id,
successMessage: 'User deleted',
);
}
Mixins
FilterableMixin
Generic filtering mixin that works with any Cubit. Adds search, sort, and filter state.
class ProductsCubit extends PaginatedCubit<Product> with FilterableMixin {
@override
void onFiltersChanged() => refresh();
@override
Future<PaginatedResponse<Product>> fetchPage(int page, int pageSize) =>
repo.getProducts(page: page, search: searchQuery, sortBy: sortBy);
}
// Also works with BaseListCubit
class StoresCubit extends BaseListCubit<Store> with FilterableMixin {
@override
void onFiltersChanged() => refresh();
}
SnackBarY
Mixin for displaying themed snackbars.
class MyWidget extends StatelessWidget with SnackBarY {
void onTap(BuildContext context) {
displaySuccessSnack(context: context, message: 'Saved!');
displayErrorSnack(context: context, message: 'Something went wrong');
}
}
Components
Bottom Sheet Selector
Searchable bottom sheet with cubit-based selection state.
BottomSheetSelectorFeature<City>(
child: BottomSheetSelectorContainer(
emptyChild: Text('Select a city'),
childBuilder: (city) => Text(city.name),
bottomSheet: SearchableBottomSheetList(
itemsProvider: () => repository.getCities(),
itemBuilder: (city) => ListTile(title: Text(city.name)),
searchHint: 'Search...',
searchFilter: (city, query) => city.name.toLowerCase().contains(query),
),
),
)
Platform
Permissions
final camera = CameraPermissionHandler();
if (await camera.g2g) { /* granted */ }
await camera.askPermIfNeeded();
Handlers: Camera, Location, Notification, Storage, ExternalStorage, MotionActivity
Connectivity
final connectivity = ConnectivityService();
final isOnline = await connectivity.isConnected;
connectivity.onStatusChange.listen((status) => print(status));
Location
final location = LocationService(positionCacheDuration: Duration(minutes: 5));
final position = await location.position;
final stream = await location.positionStream;
Utils
Formatters
Input formatters (for TextFormField):
SeparatorInputFormatter— configurable base for masked inputDateInputFormatter— DD/MM/YYYY input maskUpperCaseInputFormatter— uppercase text inputPhoneNumberInputFormatter— 555 555 55 55 formatCreditCardNumberInputFormatter— card number maskingCardExpiryInputFormatter— MM/YY card expiry
Display formatters:
DateTimeFormatter— locale-aware date/time formattingPriceFormatter— price, discount, range formatting
Validators
Configurable form validators with customizable messages.
final v = Validators(
messages: ValidatorMessages(
required: 'Required',
invalidEmail: 'Invalid email',
),
);
TextFormField(validator: v.email());
TextFormField(validator: v.password(minLength: 8));
TextFormField(validator: v.mustMatch(() => passwordController.text));
Map Launcher
Opens Apple Maps or Google Maps for a given coordinate.
final mapLauncher = MapLauncher(
errorMessage: 'Could not open maps',
);
mapLauncher.open(context: context, latitude: 41.0, longitude: 29.0);
Routing
Navigation observer for tracking route stack.
MaterialApp(
navigatorObservers: [AppNavObserver()],
)
License
MIT
Libraries
- auth/cubit/auth_cubit
- auth/cubit/auth_state
- auth/enums/unauthenticated_reason
- auth/i_auth_token_storage
- auth/implementations/auth_token_storage
- auth/objects/token_pair
- base_features/crud/crud_cubit
- base_features/crud/crud_state
- base_features/list/base_list_cubit
- base_features/list/base_list_state
- base_features/locale/locale_cubit
- base_features/operation/base_operation_cubit
- base_features/operation/base_operation_state
- base_features/paginated/paginated_cubit
- base_features/paginated/paginated_state
- components/bottom_sheet_selector/bottom_sheet_selector_container
- components/bottom_sheet_selector/bottom_sheet_selector_feature
- components/bottom_sheet_selector/cubit/bottom_sheet_selector_cubit
- components/bottom_sheet_selector/widgets/bottom_sheet_selector_empty_container
- components/bottom_sheet_selector/widgets/bottom_sheet_selector_selected_container
- components/bottom_sheet_selector/widgets/searchable_bottom_sheet_list
- core/cache/i_cache
- core/cache/implementations/default_cache
- core/cache/implementations/no_cache
- core/cache/implementations/read_only_cache
- core/cache/implementations/write_only_cache
- core/cache/objects/cache_data
- core/cache/objects/cache_type
- core/errors/app_error
- core/errors/error_category
- core/errors/error_mapper
- core/errors/error_messages
- core/errors/types/auth_error
- core/errors/types/conflict_error
- core/errors/types/network_error
- core/errors/types/not_found_error
- core/errors/types/server_error
- core/errors/types/unexpected_error
- core/errors/types/validation_error
- core/log/i_logger
- core/log/implementations/debug_logger
- core/log/objects/log_data
- core/log/objects/log_level
- core/log/printers/console_printer
- core/log/printers/i_printer
- core/notifier/i_notifier_service
- core/notifier/implementations/notifier_service
- core/notifier/objects/notifier_data
- core/storage/i_local_storage
- core/storage/implementations/secure_storage
- core/theme/y_infra_colors
- data/database/builder/command/i_db_raw_command_builder
- data/database/builder/command/implementations/create_table_command_builder
- data/database/builder/command/implementations/insert_command_builder
- data/database/builder/command/implementations/query_command_builder
- data/database/builder/query/i_query_builder
- data/database/builder/query/implementations/query_builder
- data/database/i_db_manager
- data/database/implementations/db_manager
- data/database/mapper/delete/i_db_delete_data_mapper
- data/database/mapper/insert/i_db_data_mapper
- data/database/mapper/insert/i_db_insert_data_mapper
- data/database/mapper/insert/i_db_insert_data_raw_mapper
- data/database/mapper/update/i_db_update_data_mapper
- data/database/objects/i_database_table
- data/file/controller/i_file_controller
- data/file/controller/implementations/csv_file_controller
- data/file/controller/implementations/json_file_controller
- data/file/converter/i_file_converter
- data/file/converter/implementations/map_list_file_converter
- data/file/path/i_path_provider
- data/file/path/implementations/application_support_path_provider
- data/file/path/implementations/download_path_provider
- data/network/config/base_network_config
- data/network/datasource/i_remote_datasource
- data/network/interceptors/auth_interceptor
- data/network/interceptors/interceptor_pipeline
- data/network/interceptors/logging_interceptor
- data/network/objects/paginated_response
- domain/i_repository
- firebase/analytics/objects/analytics_data
- firebase/analytics/objects/analytics_event
- firebase/analytics/objects/user_property
- firebase/analytics/services/i_analytics_service
- firebase/i_firebase_service
- firebase/messaging/configs/foreground_presentation
- firebase/messaging/listeners/i_message_listener
- firebase/messaging/services/i_messaging_service
- firebase/realtime_database/handlers/i_event_handler
- firebase/realtime_database/objects/db_reference_path
- firebase/realtime_database/services/i_realtime_database_once_service
- firebase/realtime_database/services/i_realtime_database_service
- mixins/filterable_mixin
- mixins/snackbar_y
- platform/connectivity/i_connectivity_service
- platform/connectivity/implementations/connectivity_service
- platform/location/i_location_service
- platform/location/implementations/location_service
- platform/permission/i_permission_handler
- platform/permission/implementations/camera
- platform/permission/implementations/external_storage
- platform/permission/implementations/location
- platform/permission/implementations/motion_activity
- platform/permission/implementations/notification
- platform/permission/implementations/storage
- push/configs/notification_channel_config
- push/handlers/i_remote_message_handler
- push/listeners/i_notification_listeners
- push/managers/i_push_notification_manager
- push/objects/i_custom_notification_content
- push/objects/i_custom_remote_message
- push/objects/i_custom_remote_notification
- push/objects/i_remote_message_handler_result
- push/utility/environment_interpreter
- utils/formatters/display/date_time_formatter
- utils/formatters/display/price_formatter
- utils/formatters/input/card_expiry_input_formatter
- utils/formatters/input/credit_card_number_input_formatter
- utils/formatters/input/date_input_formatter
- utils/formatters/input/phone_number_input_formatter
- utils/formatters/input/separator_input_formatter
- utils/formatters/input/upper_case_input_formatter
- utils/generators/uuid/i_uuid_provider
- utils/generators/uuid/implementations/uuid_provider
- utils/map/map_launcher
- utils/routing/objects/route_stack_item
- utils/routing/routing_base
- utils/validators/validator_messages
- utils/validators/validators
- y_infra