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 request
  • onResponse: 1st → 2nd → 3rd → response returned
  • onError: 1st → 2nd → 3rd → error returned

Any interceptor can short-circuit the pipeline:

  • handler.resolve(response) — skip remaining interceptors, return the response
  • handler.reject(error) — skip remaining interceptors, return the error
  • handler.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: OperationInitialOperationInProgressOperationSuccess<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 input
  • DateInputFormatter — DD/MM/YYYY input mask
  • UpperCaseInputFormatter — uppercase text input
  • PhoneNumberInputFormatter — 555 555 55 55 format
  • CreditCardNumberInputFormatter — card number masking
  • CardExpiryInputFormatter — MM/YY card expiry

Display formatters:

  • DateTimeFormatter — locale-aware date/time formatting
  • PriceFormatter — 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/storage/implementations/shared_preferences_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/app_nav_observer
utils/routing/objects/route_stack_item
utils/routing/routing_base
utils/validators/validator_messages
utils/validators/validators
y_infra