π 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 |
| π¨ 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
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_coreto 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
β βββ 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
Libraries
- baiomy
- egyptian_id_parser/country_id_parser_base
- egyptian_id_parser/impl/impl
- egyptian_id_parser/models/models
- egyptian_id_parser/repo/repo
- extensions/build_context_extensions
- extensions/form_auto_scroll
- extensions/validator/domain_validator
- extensions/validator/email_validator
- extensions/validator/notes_validator
- extensions/validator/password_validator
- firebase/authentication_repo
- firebase/firestore_repo
- firebase/storage_repo
- local_storage/secure_storage
- local_storage/storage_exception
- password_encryption/crypto_exception
- password_encryption/encrypted_payload
- password_encryption/hashed_password
- password_encryption/password_encryption
- password_encryption/password_hasher
- flutter_nav_kit β a type-safe, animated navigation package for Flutter.
- utils/app_input_formatters
- utils/google_maps_extractor
- utils/logger_class
- utils/text_form_field_decoration
- widgets/app_toasts
- widgets/avatar_glow
- widgets/conditional_builder
- widgets/custom_sized_box
- widgets/custom_value_listenable
- widgets/keep_alive_page
- widgets/loading/loading_item