baiomy 1.0.3 copy "baiomy: ^1.0.3" to clipboard
baiomy: ^1.0.3 copied to clipboard

A Flutter toolkit for local storage, secure storage, password encryption, Egyptian ID parsing, input validation, formatters, and reusable widgets

πŸš€ Baiomy #

Built once, used everywhere.

A powerful, all-in-one Flutter toolkit for local storage, password encryption, Egyptian ID parsing, input validation, utilities, and widgets β€” all behind a single import.

import 'package:baiomy/baiomy.dart';

πŸ“¦ What's Inside #

Module Classes / APIs
πŸ—‚οΈ Local Storage BaiomySharedPrefs Β· BaiomySecureStorage Β· StorageException
πŸ” Password Encryption BaiomyPasswordEncryption Β· PasswordHasher Β· EncryptedPayload Β· HashedPassword Β· CryptoException
🌍 Egyptian ID Parser BaiomyEgyptianIdParser
🚧 Routes BaiomyNavKit
πŸͺ„ Firebase BaiomyStorageRepo Β· BaiomyAuthRepo Β· BaiomyFirestoreRepo
🧩 Extensions BuildContextExtension · FormAutoScroll · EmailValidator · PasswordValidator · NotesValidator · DomainValidator
πŸ› οΈ Utils BaiomyInputFormatters Β· inputDecoration() Β· BaiomyLogger Β· BaiomyGoogleMapsExtractor Β· BaiomyInternetChecker
🎨 Widgets BaiomyToast · BaiomyAvatarGlow · BaiomyConditionalBuilder · CustomSizedBox · BaiomyValueListenableBuilder2 · BaiomyLoadingItem · BaiomySegmentedCircularNextButton

πŸ“₯ Installation #

dependencies:
  baiomy:
    git:
      url: https://github.com/mohamedelbaiomy/baiomy.git
flutter pub get

⚑ Setup β€” once in main.dart #

import 'package:baiomy/baiomy.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Required before using BaiomySharedPrefs
  await BaiomySharedPrefs.instance.init();

  // Required before using BaiomyPasswordEncryption
  BaiomyPasswordEncryption.instance.configure(keyPhrase: 'your-secret-phrase');

  runApp(const MyApp());
}

πŸ—‚οΈ Local Storage #

BaiomySharedPrefs #

Non-sensitive data β€” settings, flags, UI state. Backed by SharedPreferences. Reads are synchronous after init().

final prefs = BaiomySharedPrefs.instance;

// ── Write ──────────────────────────────────────────────────────────────
await prefs.setString('theme', 'dark');
await prefs.setInt('launch_count', 1);
await prefs.setBool('onboarding_done', value: true);
await prefs.setDouble('font_size', 16.0);
await prefs.setStringList('tags', ['flutter', 'dart']);
await prefs.setObject('config', {'lang': 'en', 'theme': 'dark'});

// ── Read ───────────────────────────────────────────────────────────────
final theme  = prefs.getString('theme', defaultValue: 'light');
final count  = prefs.getInt('launch_count', defaultValue: 0);
final done   = prefs.getBool('onboarding_done');
final size   = prefs.getDouble('font_size');
final tags   = prefs.getStringList('tags');
final config = prefs.getObject('config');     // Map<String, dynamic>?

// ── Update (key must already exist, throws otherwise) ──────────────────
await prefs.updateString('theme', 'light');
await prefs.updateInt('launch_count', 2);
await prefs.updateBool('onboarding_done', newValue: false);
await prefs.updateDouble('font_size', 18.0);
await prefs.patchObject('config', {'theme': 'light'}); // partial update

// ── Remove ─────────────────────────────────────────────────────────────
await prefs.remove('theme');
await prefs.clear(); // ⚠️ wipes everything

// ── Utility ────────────────────────────────────────────────────────────
prefs.containsKey('theme'); // bool
prefs.getKeys();            // Set<String>
prefs.get('theme');         // dynamic

BaiomySecureStorage #

Sensitive data β€” tokens, passwords, PII. Encrypted at rest via platform Keychain (iOS) / Keystore (Android). All reads are async.

final secure = BaiomySecureStorage.instance;

// ── Write ──────────────────────────────────────────────────────────────
await secure.setString('access_token', 'eyJhbGci...');
await secure.setBool('biometrics_enabled', value: true);
await secure.setInt('user_id', 42);
await secure.setDouble('score', 9.5);
await secure.setObject('session', {'expires_at': 1700000000});

// ── Read ───────────────────────────────────────────────────────────────
final token   = await secure.getString('access_token');
final enabled = await secure.getBool('biometrics_enabled');
final uid     = await secure.getInt('user_id');
final session = await secure.getObject('session');

// ── Update (key must already exist, throws otherwise) ──────────────────
await secure.updateString('access_token', 'newToken');
await secure.updateBool('biometrics_enabled', newValue: false);
await secure.updateInt('user_id', 99);
await secure.updateDouble('score', 10.0);
await secure.patchObject('session', {'scope': 'read write'});

// ── Remove ─────────────────────────────────────────────────────────────
await secure.remove('access_token');
await secure.removeMany(['access_token', 'session']); // batch
await secure.clear(); // ⚠️ wipes everything

// ── Utility ────────────────────────────────────────────────────────────
await secure.containsKey('access_token'); // Future<bool>
await secure.getKeys();                   // Future<Set<String>>
await secure.getAll();                    // Future<Map<String, String>>

πŸ” Password Encryption #

Which one should I use? #

Need to recover the original password later?  β†’  BaiomyPasswordEncryption  (AES-256 two-way)
Just need to verify it at login?              β†’  PasswordHasher            (PBKDF2 one-way)

BaiomyPasswordEncryption β€” Two-way AES-256-CBC #

Encrypts any string and lets you get the original value back. Every encrypt call produces a different ciphertext even for the same input because a fresh random IV is generated each time.

Configure once in main():

BaiomyPasswordEncryption.instance.configure(keyPhrase: 'your-secret-phrase');

Encrypt & store in Firestore:

final payload = BaiomyPasswordEncryption.instance.encrypt(passwordController.text);

// payload.combined   β†’ "ivBase64:ciphertextBase64"  ← store this
// payload.iv         β†’ IV used, Base64-encoded
// payload.cipherText β†’ encrypted value, Base64-encoded

await FirebaseFirestore.instance.collection('users').doc(uid).set({
  'password': payload.combined,
});

Decrypt β€” recover the original:

final doc  = await FirebaseFirestore.instance.collection('users').doc(uid).get();
final pass = BaiomyPasswordEncryption.instance.decrypt(doc['password'] as String);

Convenience β€” encrypt directly to a string:

final stored = BaiomyPasswordEncryption.instance.encryptToString(passwordController.text);

Validate a stored value:

BaiomyPasswordEncryption.instance.isValidPayload(stored); // bool

PasswordHasher β€” One-way PBKDF2-HMAC-SHA256 #

Best for login systems where you never need the original password back. Uses 310,000 iterations (OWASP 2023) + a unique 32-byte random salt. Uses constant-time comparison to prevent timing attacks. The original password cannot be recovered β€” ever.

Hash on registration:

final hashed = PasswordHasher.instance.hash(passwordController.text);

// hashed.combined   β†’ "310000:saltBase64:hashBase64"  ← store this
// hashed.hash       β†’ derived key, Base64-encoded
// hashed.salt       β†’ random salt, Base64-encoded
// hashed.iterations β†’ 310000

await FirebaseFirestore.instance.collection('users').doc(uid).set({
  'passwordHash': hashed.combined,
});

Verify on login:

final doc = await FirebaseFirestore.instance.collection('users').doc(uid).get();

final ok = PasswordHasher.instance.verify(
  password: passwordController.text,
  combined: doc['passwordHash'] as String,
);

if (!ok) throw Exception('Wrong password');

Validate a stored hash string:

PasswordHasher.instance.isValidHash(storedValue); // bool

🌍 Egyptian ID Parser #

Parse and extract full information from a 14-digit Egyptian National ID.

final parser = BaiomyEgyptianIdParser('29901011234567');

print(parser.birthDate);    // e.g. "1999-01-01"
print(parser.governorate);  // e.g. "Cairo"
print(parser.gender);       // e.g. "Male"
print(parser.age);          // Age object

🧩 Extensions #

BuildContextExtension #

// Navigation
context.pop();
context.popWithValue('result');
await context.mayBePop(); // Future<bool>

// Screen dimensions
final width  = context.screenWidth;   // double
final height = context.screenHeight;  // double
final dpr    = context.devicePixelRatio; // double

FormAutoScroll #

Automatically scrolls to the first invalid field on form submission. Called as an extension on GlobalKey<FormState>:

final _formKey = GlobalKey<FormState>();

// Instead of _formKey.currentState!.validate()
final isValid = _formKey.validateAndScroll(); // bool
// Scrolls to the first field with an error if invalid

EmailValidator #

'user@gmail.com'.isValidEmail();          // true
'not-an-email'.isValidEmail();            // false
'user@uni.edu.eg'.isAcademicEmail();      // true
'user@company.com'.isCorporateEmail();    // true
'test@test.com'.hasSuspiciousEmailPattern(); // true
'user@gmail.com'.capitalize();            // 'User@gmail.com'

PasswordValidator #

'MyPass1!'.hasUppercase();                // true
'MyPass1!'.hasLowercase();                // true
'MyPass1!'.hasDigit();                    // true
'MyPass1!'.hasSpecialCharacter();         // true
'MyPass1!'.hasWhitespace();              // false
'MyPass1!'.hasMixedCase();               // true
'MyPass1!'.hasMultipleDigits();          // false
'MyPass1!'.hasMultipleSpecialChars();    // false
'password123'.isCommonPassword();         // true
'abc123'.hasSequentialCharacters();       // true
'aaabbb'.hasExcessiveRepeatedCharacters(); // true

NotesValidator #

'spam content'.hasInappropriateContent();       // true
'Hello!!!!!!'.hasExcessiveSpecialCharacters();  // true
'aaaaaaa note'.hasExcessiveRepeatedText();      // true
'Study near the library'.hasMeaningfulContent(); // true
'Valid Note.'.hasProperStructure();             // true
'Room 101 level 2'.hasSpecificDetails();        // true
'Near the faculty building'.hasLocationDetails(); // true
'Near university campus'.hasEducationalContext(); // true
'Hello world'.getWordCount();                   // 2
'Hello world'.getCharacterCountWithoutSpaces(); // 10

DomainValidator #

'flutter.dev'.isDomainValid();          // true
'mail.uni.edu.eg'.isDomainValid();      // true
'invalid'.isDomainValid();              // false
'Ω…Ψ«Ψ§Ω„.com'.isInternationalDomain();    // true

BaiomyNavKit #

Features #

Feature Detail
Named route validation Typos are caught at debug-time via assert
5 transition styles bottomToUpWithFade Β· fade Β· slideWithFade Β· slideOnly Β· cupertino
Platform-aware defaults Cupertino on iOS, fade/slide on Android
Semantic push methods pushWithSlideUp, pushWithFade, pushWithSlideAndFade, pushWithSlide
Replace / clear-stack pushReplacement, pushAndRemoveAll, replaceStack
Direct widget push No route name required β€” great for modal sheets
BuildContext extensions context.navPush(...) Β· context.navPop() Β· context.navCanPop
Zero dependencies Only Flutter itself

Usage #

1 β€” Declare your routes #

Create one file that owns every route string in your app. Extending NavRoutes and listing them in get all is what enables debug-time typo detection.

// lib/routes/routes.dart
import 'package:flutter_nav_kit/flutter_nav_kit.dart';
 
class Routes extends NavRoutes {
  static const String home    = '/home';
  static const String login   = '/login';
  static const String detail  = '/detail';
  static const String profile = '/profile';
 
  @override
  List<String> get all => [home, login, detail, profile];
}

2 β€” Initialise and wire up #

Call BaiomyNavKit.init once before runApp, then pass NavRouteGenerator to MaterialApp.onGenerateRoute. The builders map is the only place you ever write the mapping between a route name and a screen widget.

// lib/main.dart
import 'package:flutter_nav_kit/flutter_nav_kit.dart';
import 'routes/routes.dart';
 
void main() {
  BaiomyNavKit.init(routes: Routes()); // turns on assert-based route validation
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: Routes.home,
      onGenerateRoute: NavRouteGenerator(
        routes: Routes(),
        noRouteScreen: const NotFoundScreen(), // shown for unknown routes
        firstRoute: Routes.home,
        builders: {
          // args is the value you passed via the `arguments:` parameter,
          // with all transition metadata already stripped out for you.
          Routes.home:    (_)    => const HomeScreen(),
          Routes.login:   (_)    => const LoginScreen(),
          Routes.detail:  (args) => DetailScreen(item: args as MyItem),
          Routes.profile: (args) => ProfileScreen(userId: args as String),
        },
      ).generateRoute,
    );
  }
}

3 β€” Push screens #

Simple push β€” platform-default transition

On iOS this uses the native Cupertino swipe; on Android it uses a fade. No configuration required.

BaiomyNavKit.push(context, Routes.home);
BaiomyNavKit.push(context, Routes.detail, arguments: myItem);

Push with a specific animation

BaiomyNavKit.pushWithSlideUp(context, Routes.detail, arguments: myItem);
BaiomyNavKit.pushWithFade(context, Routes.settings);
BaiomyNavKit.pushWithSlide(context, Routes.profile, arguments: userId);
BaiomyNavKit.pushWithSlideAndFade(context, Routes.detail);

Replace the current screen (no back button left behind)

BaiomyNavKit.pushReplacement(context, Routes.home);
BaiomyNavKit.pushReplacementWithFade(context, Routes.login);
BaiomyNavKit.pushReplacementWithSlideUp(context, Routes.onboarding);

Clear the entire stack (logout, post-login redirect, splash β†’ home)

BaiomyNavKit.pushAndRemoveAll(context, Routes.login);
BaiomyNavKit.pushAndRemoveAllWithFade(context, Routes.home);
BaiomyNavKit.pushAndRemoveAllWithSlideUp(context, Routes.dashboard);

Replace the whole stack with multiple screens

Useful for deep-link handling β€” clears the stack, pushes the first route, then pushes the rest on top so the back-stack is exactly as you specify.

BaiomyNavKit.replaceStack(context, [Routes.home, Routes.detail]);

Avoid pushing the same screen twice

// No-op if the user is already on Routes.home
BaiomyNavKit.pushIfNotCurrent(context, Routes.home);

4 β€” Pop / go back #

BaiomyNavKit.pop(context);                     // simple back
BaiomyNavKit.pop(context, myResult);           // back with a return value
BaiomyNavKit.popUntil(context, Routes.home);   // unwind to a specific screen
BaiomyNavKit.canPop(context);                  // β†’ bool

5 β€” Push a widget directly (no route name needed) #

Good for modals, confirmation sheets, or one-off screens you don't want registered in the route table.

// Lazy builder β€” widget isn't constructed until the transition begins
BaiomyNavKit.pushWidget(context, () => const ConfirmationSheet());
 
// Or pass an already-constructed widget
BaiomyNavKit.pushWidgetWithSlideUp(context, const ConfirmationSheet());
BaiomyNavKit.pushWidgetWithFade(context, const ConfirmationSheet());

6 β€” Custom transition durations #

Every method accepts optional transitionDuration and reverseTransitionDuration. Omit them to use the built-in defaults.

BaiomyNavKit.pushWithSlideUp(
  context,
  Routes.detail,
  arguments: myItem,
  transitionDuration: const Duration(milliseconds: 500),
  reverseTransitionDuration: const Duration(milliseconds: 350),
);

7 β€” Context extensions (optional shorthand) #

Every BaiomyNavKit method has a matching extension on BuildContext prefixed with nav. Use whichever style fits your codebase β€” they are identical under the hood.

// These two lines do exactly the same thing:
BaiomyNavKit.pushWithSlideUp(context, Routes.detail, arguments: item);
context.navPushWithSlideUp(Routes.detail, arguments: item);
 
// Other examples
context.navPush(Routes.home);
context.navPushWithFade(Routes.settings);
context.navPushReplacement(Routes.login);
context.navPushAndRemoveAll(Routes.home);
context.navPop();
context.navPopUntil(Routes.home);
bool can = context.navCanPop;
context.navPushWidgetWithSlideUp(const MyModal());

πŸ› οΈ Utils #

BaiomyInputFormatters #

Apply as inputFormatters on any TextFormField:

// ── Ready-made formatters ──────────────────────────────────────────────
TextFormField(inputFormatters: BaiomyInputFormatters.nameField);
TextFormField(inputFormatters: BaiomyInputFormatters.phoneField);
TextFormField(inputFormatters: BaiomyInputFormatters.emailField);
TextFormField(inputFormatters: BaiomyInputFormatters.passwordField);
TextFormField(inputFormatters: BaiomyInputFormatters.notesField);
TextFormField(inputFormatters: BaiomyInputFormatters.cleanText);
TextFormField(inputFormatters: BaiomyInputFormatters.username);
TextFormField(inputFormatters: BaiomyInputFormatters.creditCard);
TextFormField(inputFormatters: BaiomyInputFormatters.currency);

// ── Basic formatters ───────────────────────────────────────────────────
BaiomyInputFormatters.denyEmojis
BaiomyInputFormatters.numbersOnly
BaiomyInputFormatters.lettersOnly
BaiomyInputFormatters.alphanumericOnly
BaiomyInputFormatters.phoneNumberSafe
BaiomyInputFormatters.emailSafe
BaiomyInputFormatters.urlSafe
BaiomyInputFormatters.passwordSafe
BaiomyInputFormatters.denyProfanity

// ── With length limits ─────────────────────────────────────────────────
BaiomyInputFormatters.lengthLimit(10)
BaiomyInputFormatters.nameWithLength(35)      // default 35
BaiomyInputFormatters.phoneWithLength(11)     // default 11
BaiomyInputFormatters.notesWithLength(500)    // default 500

// ── Custom ─────────────────────────────────────────────────────────────
BaiomyInputFormatters.customDeny([RegExp(r'[xyz]')], allowEmojis: false)
BaiomyInputFormatters.customAllow([RegExp(r'[0-9]')])
BaiomyInputFormatters.caseFormatter(uppercase: true)

// ── Validation helpers (no TextFormField needed) ───────────────────────
BaiomyInputFormatters.containsEmojis('hello 😊');      // true
BaiomyInputFormatters.isNumericOnly('12345');           // true
BaiomyInputFormatters.containsProfanity('some text');  // false
BaiomyInputFormatters.getCleanCharacterCount('hi 😊'); // 3

// ── Internet Checker ──────────────────────────────────────────────────
final checker = BaiomyInternetChecker.instance;
// Check current status
bool isOnline = await checker.hasConnection();
// Listen for real-time changes
checker.onStatusChange.listen((status) {
  if (status == BaiomyInternetStatus.online) {
    print('Back online!');
  } else {
    print('Connection lost.');
  }
});
// Use named states
if (checker.currentStatus == InternetStatus.connected) { ... }

// ── Logger Class ──────────────────────────────────────────────────
final logger = BaiomyLogger.instance;
logger.debug('This is a debug message'); // πŸ’‘ [DEBUG] ...
logger.info('User logged in');          // ℹ️ [INFO] ...
logger.warning('Low storage space');    // ⚠️ [WARNING] ...
logger.error('Failed to load data', error: e, stackTrace: s); // ❌ [ERROR] ...

// ── Google Maps Extractor ──────────────────────────────────────────
final url = 'https://maps.app.goo.gl/mWtb4a1cUE9zMWya7';
// 1. Simple extraction (handles shortening)
final coords = await BaiomyGoogleMapsExtractor.processGoogleMapsUrl(url);
if (coords != null) {
  print('Lat: ${coords['latitude']}, Lng: ${coords['longitude']}');
}
// 2. Extract metadata (zoom, place name, etc.)
final metadata = await BaiomyGoogleMapsExtractor.extractMetadata(url);
print('Place: ${metadata['placeName']}');
print('Zoom: ${metadata['zoom']}');
// 3. Validation
if (BaiomyGoogleMapsExtractor.isGoogleMapsUrl(url)) {
  print('Valid Google Maps link');
}

inputDecoration() #

A global function that returns a styled InputDecoration:

// Underline style (default)
TextFormField(
  decoration: inputDecoration(
    'Enter your email',
    Theme.of(context),
    suffixIcon: const Icon(Icons.email),
    helperText: 'We will never share your email',
  ),
)

// Outlined style
TextFormField(
  decoration: inputDecoration(
    'Enter your password',
    Theme.of(context),
    isOutlined: true,
    suffixIcon: const Icon(Icons.lock),
  ),
)

BaiomyGoogleMapsExtractor #

Extracts latitude and longitude coordinates from any Google Maps URL format.

final url = 'https://maps.app.goo.gl/mWtb4a1cUE9zMWya7';
final coordinates = await BaiomyGoogleMapsExtractor.processGoogleMapsUrl(url);

if (coordinates != null) {
print('Latitude: ${coordinates['latitude']}');
print('Longitude: ${coordinates['longitude']}');
} else {
print('Failed to extract coordinates');
}

Supported URL formats:

  • Standard map URLs with coordinates
  • Place URLs with embedded coordinates
  • Shortened URLs (goo.gl, maps.app.goo.gl)
  • Street View URLs
  • Directions URLs
  • Embedded map URLs
  • Mobile deep links
  • Plus codes
  • International Google domain variants

Available methods:

// Process any Google Maps URL (handles shortening + extraction)
final coords = await BaiomyGoogleMapsExtractor.processGoogleMapsUrl(url);

// Extract coordinates from an already-expanded URL
final coords = BaiomyGoogleMapsExtractor.extractCoordinates(expandedUrl);

// Check if a URL is a Google Maps URL
final bool isMaps = BaiomyGoogleMapsExtractor.isGoogleMapsUrl(url);

// Extract additional metadata (zoom level, map type, place name)
final metadata = BaiomyGoogleMapsExtractor.extractMetadata(url);
// Returns: { 'zoom': 15, 'mapType': 'roadmap', 'placeName': 'Cairo Tower' }

πŸ”₯ Firebase #

All Firebase modules require firebase_core to be initialized before use.


BaiomyAuthRepo β€” Authentication #

A singleton repository that wraps Firebase Authentication with clean, async APIs for email/password, guest accounts, profile updates, and guest-to-permanent upgrades.

final auth = BaiomyAuthRepo.instance;

// ── Sign up / Sign in ──────────────────────────────────────────────────
final credential = await auth.signUpWithEmailAndPassword(email, password);
final credential = await auth.signInWithEmailAndPassword(email, password);
final guestCred = await auth.signInAnonymously();

// ── Current user state ─────────────────────────────────────────────────
User? user = auth.currentUser;
String? uid = auth.uid;
bool signedIn = auth.isSignedIn;
bool guest = auth.isGuest;
Stream<User?> authChanges = auth.authStateChanges;

// ── Email verification ─────────────────────────────────────────────────
await auth.sendEmailVerification();
bool isVerified = await auth.checkEmailVerification();

// ── Profile updates ────────────────────────────────────────────────────
await auth.updateUserName('John Doe');
await auth.updateUserPassword('newSecurePass123');

// ── Password reset ─────────────────────────────────────────────────────
await auth.sendPasswordResetEmail(email);
bool oldPasswordOk = await auth.checkOldPassword(email, oldPassword);

// ── Guest β†’ permanent upgrade ──────────────────────────────────────────
await auth.upgradeGuestToUser(
email: 'user@example.com',
password: 'newPassword',
name: 'John',
phone: '+201234567890',
);

// ── Sign out ───────────────────────────────────────────────────────────
await auth.logOut();

// ── User-friendly error messages ───────────────────────────────────────
try {
await auth.signInWithEmailAndPassword(email, password);
} on FirebaseAuthException catch (e) {
final message = auth.getAuthErrorMessage(e);
showToast(message);
}

BaiomyFirestoreRepo β€” Cloud Firestore #

A singleton repository providing CRUD, real-time streams, pagination, batch writes, and transactions for Firestore.

final firestore = BaiomyFirestoreRepo.instance;

// ── Write ──────────────────────────────────────────────────────────────
// Create / overwrite a document
await firestore.createCollectionWithDoc(
collectionName: 'users',
docName: 'uid123',
data: {'name': 'Alice', 'email': 'alice@example.com'},
);

// Auto‑generated document ID
final docRef = await firestore.createCollection(
collectionName: 'posts',
data: {'title': 'Hello', 'content': '...'},
);

// Update specific fields
await firestore.updateData(
collectionName: 'users',
docName: 'uid123',
data: {'lastLogin': FieldValue.serverTimestamp()},
);

// Sub‑collection operations
await firestore.createSubCollectionWithDoc(
firstCollectionName: 'users',
secondCollectionName: 'orders',
firstDocName: 'uid123',
secondDocName: 'order456',
data: {'total': 99.99},
);

// ── Read ───────────────────────────────────────────────────────────────
// Get all documents
final QuerySnapshot allUsers = await firestore.getData(collectionName: 'users');

// Get single document
final DocumentSnapshot userDoc = await firestore.getDocData('users', 'uid123');

// Query with conditions
final QuerySnapshot adults = await firestore.getDataWhere(
collectionName: 'users',
field: 'age',
value: 18,
orderByField: 'name',
limit: 10,
);

// Pagination
final firstPage = await firestore.getDataWithPagination(
collectionName: 'users',
limit: 20,
orderByField: 'createdAt',
);
final lastDoc = firstPage.docs.last;
final secondPage = await firestore.getDataWithPagination(
collectionName: 'users',
limit: 20,
lastDocument: lastDoc,
);

// Check existence
final exists = await firestore.documentExists(
collectionName: 'users',
docName: 'uid123',
);

// ── Real‑time streams ─────────────────────────────────────────────────
// Single document stream
Stream<DocumentSnapshot> userStream = firestore.documentStream(
collectionName: 'users',
docName: 'uid123',
);

// Collection stream with filters
Stream<QuerySnapshot> recentPosts = firestore.collectionStream(
collectionName: 'posts',
whereField: 'published',
whereValue: true,
orderByField: 'timestamp',
descending: true,
limit: 50,
);

// Sub‑collection stream
Stream<QuerySnapshot> orderStream = firestore.subCollectionStream(
firstCollectionName: 'users',
secondCollectionName: 'orders',
docName: 'uid123',
);

// ── Batch & transactions ───────────────────────────────────────────────
// Batch write
await firestore.batchWrite((batch, db) {
final userRef = db.collection('users').doc('uid123');
batch.update(userRef, {'points': 10});
final logRef = db.collection('logs').doc();
batch.set(logRef, {'action': 'add_points'});
});

// Transaction
await firestore.runTransaction((tx) async {
final snap = await tx.get(userRef);
final newPoints = (snap.data()?['points'] ?? 0) + 10;
tx.update(userRef, {'points': newPoints});
});

// ── Delete ─────────────────────────────────────────────────────────────
// Delete single document
await firestore.deleteData(collectionName: 'users', documentId: 'uid123');

// Delete all documents in a sub‑collection
await firestore.deleteSubCollection(
firstCollectionName: 'users',
secondCollectionName: 'orders',
docName: 'uid123',
);

BaiomyStorageRepo β€” Firebase Storage #

A singleton repository for uploading, downloading, updating, and deleting files with progress tracking and metadata support.

final storage = BaiomyStorageRepo.instance;

// ── Upload ─────────────────────────────────────────────────────────────
// Upload a local file
final imageUrl = await storage.uploadFile(
path: 'users/uid123/avatar.jpg',
file: File('/local/avatar.jpg'),
metadata: SettableMetadata(contentType: 'image/jpeg'),
);

// Upload raw bytes (e.g., from memory)
final pdfUrl = await storage.uploadBytes(
path: 'invoices/invoice123.pdf',
bytes: pdfBytes,
);

// Upload from a string (base64, text, etc.)
final url = await storage.uploadFromString(
path: 'logs/error.log',
fileUrl: errorLogText,
format: PutStringFormat.plain,
);

// Upload with progress tracking
final task = storage.uploadFileWithProgress(
path: 'videos/intro.mp4',
file: videoFile,
);
task.snapshotEvents.listen((snapshot) {
final progress = snapshot.bytesTransferred / snapshot.totalBytes;
print('${(progress * 100).toStringAsFixed(1)}%');
});
final snapshot = await task;
final videoUrl = await snapshot.ref.getDownloadURL();

// ── Read / download ────────────────────────────────────────────────────
// Get download URL
final url = await storage.getDownloadUrl(path: 'users/uid123/avatar.jpg');

// Get file metadata
final FullMetadata meta = await storage.getMetadata(path: 'files/data.json');
print('Size: ${meta.size}, Type: ${meta.contentType}');

// Download bytes (max 10 MB by default)
final Uint8List? data = await storage.downloadBytes(
path: 'documents/report.pdf',
maxSize: 5 * 1024 * 1024, // 5 MB
);

// List files in a folder
final ListResult result = await storage.listItems(path: 'users/uid123');
for (final ref in result.items) {
print(ref.name);
}

// Recursive list all (use with caution on large trees)
final ListResult all = await storage.listAll(path: 'users');

// Paginated listing
final page1 = await storage.listItemsPaginated(
path: 'photos',
maxResults: 20,
);
final page2 = await storage.listItemsPaginated(
path: 'photos',
maxResults: 20,
pageToken: page1.nextPageToken,
);

// Check if a file exists
final exists = await storage.fileExists(path: 'users/uid123/avatar.jpg');

// ── Update ─────────────────────────────────────────────────────────────
// Replace entire file
await storage.updateFile(
path: 'users/uid123/avatar.jpg',
newFile: File('/new_avatar.jpg'),
);

// Replace with bytes
await storage.updateBytes(
path: 'users/uid123/avatar.jpg',
newBytes: newImageBytes,
);

// Update only metadata (no re‑upload)
await storage.updateMetadata(
path: 'users/uid123/avatar.jpg',
metadata: SettableMetadata(customMetadata: {'uploadedBy': 'admin'}),
);

// ── Delete ─────────────────────────────────────────────────────────────
// Delete a single file
await storage.deleteFile(path: 'users/uid123/old_avatar.jpg');

// Delete all files inside a folder (non‑recursive)
await storage.deleteFolder(path: 'temp/session123');

// Recursively delete entire folder tree (⚠️ use with caution)
await storage.deleteAll(path: 'users/uid123_old');

⚠️ Important: For large‑scale delete operations (thousands of files), prefer a Cloud Function to avoid timeouts and excessive client‑side work.


🎨 Widgets #

Widget files are in lib/widgets/. Refer to each file for full constructor details as the APIs depend on your local implementation.

BaiomyToast #

Quick snackbar-style notifications.

BaiomyAvatarGlow #

Avatar widget with an animated glow effect.

BaiomyConditionalBuilder #

Renders different widgets based on a condition.

CustomSizedBox #

Convenient spacing widget using extension.

BaiomyValueListenableBuilder2 #

Reactive widget that rebuilds when a ValueListenable changes.

BaiomyLoadingItem #

Loading skeleton / overlay widget (from widgets/loading/loading_item.dart).

BaiomyKeepAlivePage #

utility wrapper widget designed to preserve the state of its child widget, preventing it from being disposed of when it moves out of view

Segmented Circular Next Button #

provides a circular next button with an animated segmented progress ring for onboarding flows


πŸ›‘οΈ Error Handling #

Every module throws its own typed exception β€” never a raw platform error.

// Storage errors
try {
  await BaiomySecureStorage.instance.updateString('missing_key', 'value');
} on StorageException catch (e) {
  print(e.message);    // 'Cannot update a key that does not exist.'
  print(e.key);        // 'missing_key'
  print(e.cause);      // original platform error
  print(e.stackTrace); // original stack trace
}

// Crypto errors
try {
  BaiomyPasswordEncryption.instance.decrypt('bad_format');
} on CryptoException catch (e) {
  print(e.message); // 'Decryption failed. The key may be wrong...'
  print(e.cause);   // original error
}

πŸ“ Package Structure #

lib/
β”œβ”€β”€ egyptian_id_parser/
β”‚   β”œβ”€β”€ impl/
β”‚   β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ repo/
β”‚   └── country_id_parser_base.dart    β†’ BaiomyEgyptianIdParser
β”œβ”€β”€ extensions/
β”‚   β”œβ”€β”€ validator/
β”‚   β”‚   β”œβ”€β”€ domain_validator.dart      β†’ DomainValidator (extension)
β”‚   β”‚   β”œβ”€β”€ email_validator.dart       β†’ EmailValidator (extension)
β”‚   β”‚   β”œβ”€β”€ notes_validator.dart       β†’ NotesValidator (extension)
β”‚   β”‚   └── password_validator.dart    β†’ PasswordValidator (extension)
β”‚   β”œβ”€β”€ build_context_extensions.dart  β†’ BuildContextExtension (extension)
β”‚   └── form_auto_scroll.dart          β†’ FormAutoScroll (extension on GlobalKey)
β”œβ”€β”€ local_storage/
β”‚   β”œβ”€β”€ shared_preferences.dart        β†’ BaiomySharedPrefs
β”‚   β”œβ”€β”€ secure_storage.dart            β†’ BaiomySecureStorage
β”‚   └── storage_exception.dart         β†’ StorageException
β”œβ”€β”€ routes/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ nav_extensions.dart      β†’ NavKitContext (extension)
β”‚   β”‚   β”œβ”€β”€ nav_kit.dart             β†’ BaiomyNavKit
β”‚   β”‚   β”œβ”€β”€ nav_route_generator.dart β†’ NavRouteGenerator
β”‚   β”‚   β”œβ”€β”€ nav_routes.dart          β†’ NavRoutes
β”‚   β”‚   └── nav_transition_type.dart β†’ NavTransitionType (enum)
β”‚   └── flutter_nav_kit.dart
β”œβ”€β”€ password_encryption/
β”‚   β”œβ”€β”€ password_encryption.dart       β†’ BaiomyPasswordEncryption
β”‚   β”œβ”€β”€ password_hasher.dart           β†’ PasswordHasher
β”‚   β”œβ”€β”€ encrypted_payload.dart         β†’ EncryptedPayload
β”‚   β”œβ”€β”€ hashed_password.dart           β†’ HashedPassword
β”‚   └── crypto_exception.dart          β†’ CryptoException
β”œβ”€β”€ firebase/
β”‚   β”œβ”€β”€ firestore_repo.dart           β†’ BaiomyFirestoreRepo
β”‚   β”œβ”€β”€ authentication_repo.dart      β†’ BaiomyAuthRepo
β”‚   └── storage_repo.dart             β†’ BaiomyStorageRepo
β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ app_input_formatters.dart       β†’ BaiomyInputFormatters
β”‚   β”œβ”€β”€ logger_class.dart               β†’ BaiomyLogger
β”‚   β”œβ”€β”€ google_maps_extractor.dart      β†’ BaiomyGoogleMapsExtractor
β”‚   β”œβ”€β”€ baiomy_internet_checker.dart    β†’ BaiomyInternetChecker
β”‚   └── text_form_field_decoration.dart β†’ inputDecoration()
β”œβ”€β”€ widgets/
β”‚   β”œβ”€β”€ loading/
β”‚   β”‚   └── loading_item.dart
β”‚   β”œβ”€β”€ app_toasts.dart
β”‚   β”œβ”€β”€ avatar_glow.dart
β”‚   β”œβ”€β”€ conditional_builder.dart
β”‚   β”œβ”€β”€ custom_sized_box.dart
β”‚   β”œβ”€β”€ segmented_circular_next_button.dart
β”‚   └── custom_value_listenable.dart
└── baiomy.dart

βš–οΈ License #

BAIOMY PROPRIETARY SOFTWARE LICENSE

================================================================================
Copyright (c) 2026 Mohamed Elbaiomy. All Rights Reserved.
================================================================================

IMPORTANT β€” READ CAREFULLY BEFORE USING THIS SOFTWARE.

This license agreement ("Agreement") is a legal agreement between you
("Licensee") and the author of this software package ("Licensor", "Mohamed Elbaiomy").
By accessing, downloading, copying, installing, or using any part of this
software, its source code, documentation, or any associated files
(collectively, the "Software"), you agree to be bound by the terms of this
Agreement. If you do not agree, you must immediately delete all copies of the
Software and cease all use.

────────────────────────────────────────────────────────────────────────────────
1. GRANT OF LICENSE
────────────────────────────────────────────────────────────────────────────────

No license is granted under this Agreement unless explicitly authorized in
writing by Mohamed Elbaiomy. Mohamed reserves ALL rights to this Software,
including but not limited to the right to use, copy, modify, merge, publish,
distribute, sublicense, and sell copies of the Software.

────────────────────────────────────────────────────────────────────────────────
2. RESTRICTIONS
────────────────────────────────────────────────────────────────────────────────

Unless you have received prior WRITTEN PERMISSION from the Licensor, you are
STRICTLY PROHIBITED from:

  a) Using this Software, in whole or in part, for any personal, academic,
     commercial, or non-commercial project.

  b) Copying, reproducing, or duplicating the Software or any portion thereof.

  c) Modifying, adapting, translating, reverse engineering, decompiling,
     disassembling, or creating derivative works based on the Software.

  d) Distributing, publishing, sublicensing, selling, renting, leasing, or
     transferring the Software or any rights therein to any third party.

  e) Incorporating the Software or any part of it into another software
     package, library, product, or service.

  f) Removing, altering, or obscuring any copyright notice, license text,
     attribution, or proprietary marking included in the Software.

  g) Using the name "Baiomy", the package name, or any associated branding
     to endorse or promote products derived from this Software without prior
     written consent.

  h) Uploading, mirroring, or re-hosting this Software on any public or
     private repository, platform, or registry (including but not limited to
     pub.dev, GitHub, GitLab, Bitbucket, or npm) without explicit written
     authorization from the Licensor.

────────────────────────────────────────────────────────────────────────────────
3. OWNERSHIP
────────────────────────────────────────────────────────────────────────────────

The Software is proprietary to Mohamed Elbaiomy and is protected by copyright law
and international treaty provisions. The Licensor retains exclusive ownership
of all intellectual property rights in and to the Software, including all
copies, modifications, and derivative works, regardless of who created them.

This Agreement does not transfer any ownership, title, or intellectual property
rights in the Software to the Licensee. Any use of the Software not expressly
permitted by this Agreement is strictly prohibited and constitutes a violation
of the Licensor's intellectual property rights.

────────────────────────────────────────────────────────────────────────────────
4. NO WARRANTIES
────────────────────────────────────────────────────────────────────────────────

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, OR NON-INFRINGEMENT. THE LICENSOR
DOES NOT WARRANT THAT THE SOFTWARE WILL MEET YOUR REQUIREMENTS, OPERATE WITHOUT
INTERRUPTION, OR BE ERROR-FREE.

────────────────────────────────────────────────────────────────────────────────
5. LIMITATION OF LIABILITY
────────────────────────────────────────────────────────────────────────────────

IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, CONSEQUENTIAL, OR PUNITIVE DAMAGES (INCLUDING BUT NOT
LIMITED TO LOSS OF DATA, LOSS OF PROFITS, BUSINESS INTERRUPTION, OR LOSS OF
GOODWILL) ARISING OUT OF OR IN CONNECTION WITH THE USE OR INABILITY TO USE
THE SOFTWARE, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

────────────────────────────────────────────────────────────────────────────────
6. ENFORCEMENT
────────────────────────────────────────────────────────────────────────────────

Any unauthorized use, reproduction, distribution, or modification of this
Software constitutes copyright infringement and will be subject to civil and
criminal penalties under applicable law, including international copyright
treaties. The Licensor reserves the right to seek all available legal and
equitable remedies, including injunctive relief and monetary damages.

────────────────────────────────────────────────────────────────────────────────
7. TERMINATION
────────────────────────────────────────────────────────────────────────────────

Any rights conditionally granted by Mohamed Elbaiomy (if any) terminate
immediately and automatically upon any breach of this Agreement by the
Licensee. Upon termination, the Licensee must destroy all copies of the
Software in their possession or control.

────────────────────────────────────────────────────────────────────────────────
8. GOVERNING LAW
────────────────────────────────────────────────────────────────────────────────

This Agreement shall be governed by and construed in accordance with the laws
of the Arab Republic of Egypt, without regard to its conflict of law provisions.
Any dispute arising under or relating to this Agreement shall be subject to the
exclusive jurisdiction of the competent courts of Egypt.

────────────────────────────────────────────────────────────────────────────────
9. CONTACT
────────────────────────────────────────────────────────────────────────────────

For licensing inquiries, permission requests, or any questions regarding this
Agreement, contact the Licensor at:

  Author  : Mohamed Elbaiomy
  Project : https://github.com/mohamedelbaiomy/baiomy

────────────────────────────────────────────────────────────────────────────────
ALL RIGHTS RESERVED. UNAUTHORIZED USE IS STRICTLY PROHIBITED.
────────────────────────────────────────────────────────────────────────────────


Built with ❀️ by Baiomy

1
likes
140
points
75
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter toolkit for local storage, secure storage, password encryption, Egyptian ID parsing, input validation, formatters, and reusable widgets

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

cloud_firestore, encrypt, firebase_auth, firebase_storage, flutter, flutter_secure_storage, http, pointycastle, shared_preferences, skeletonizer

More

Packages that depend on baiomy