common_utils2 2.0.1
common_utils2: ^2.0.1 copied to clipboard
A comprehensive Flutter utilities package with 24+ utilities for rapid app development. Includes banking, currency conversion, networking, and more.
Common Utils2 #
A comprehensive Flutter utilities package that provides essential tools for rapid app development. From string manipulation to video preloading, currency conversion to push notifications - everything you need in one package.
✨ Features #
🎥 Video & Media Management #
New in v2.0.0
- Video Preloading: Smart video controller with configurable preload-ahead/behind
- Pagination Support: Automatic content loading as users scroll
- Mixed Media Feeds: Facebook-style feeds with videos, images, and documents
- Lazy Video Loading: Viewport-aware video initialization for optimal memory
- State Preservation: Like/unlike without reloading media
- Generic Design: Works with any data type (Posts, Reels, Stories)
📥 Download Manager #
- Queue System: Max 3 concurrent downloads with automatic queueing
- Pause/Resume: Full control over download lifecycle
- Progress Tracking: Real-time progress with speed and ETA
- Multi-Format: Videos, images, documents, any file type
- Storage Management: Organized by type (Videos/, Images/, Documents/)
- Batch Operations: Pause all, resume all, cancel all
🔔 Push Notifications #
- FCM Integration: Complete Firebase Cloud Messaging support
- Local Notifications: Flutter local notifications with channels
- In-App Toasts: Slide-down notification overlays
- Notification Centre: Full history with read/unread tracking
- Badge Support: Unread count badges for nav icons
- Deep Linking: Automatic routing from notification taps
- Generic Handler: Works with any app-specific routing logic
🖼️ Image Preloading #
- Smart Caching: Two-layer caching (network + memory)
- Scroll-Ahead: Preloads images in scroll direction
- CachedNetworkImage: Optimized image display widgets
- Memory Efficient: Only keeps visible + buffer
📄 Document Previews #
- PDF Thumbnails: Generate first-page previews
- Multiple Formats: PDF, DOCX, XLSX, PPTX support
- Generic Icons: Fallback for unsupported formats
🔤 String & Text Utilities #
- 50+ String Extensions: Validation, capitalization, masking, truncation
- Validators: Email, phone, passwords, Nigerian-specific (BVN, NIN)
- Regex Patterns: 50+ predefined patterns for common use cases
- Format Utilities: Phone numbers, credit cards, dates, file sizes
🔢 Number & Math #
- Number Extensions: Currency formatting, percentages, compact notation
- Math Utilities: Statistics, financial calculations, random generation
- Currency Converter: Real-time exchange rates for 150+ currencies
- Nigerian Naira Support: Built-in ₦ formatting
🏦 Banking & Finance #
- Bank List: Fetch banks for Nigeria, Ghana, South Africa, Kenya
- Account Validation: Real-time account verification via Paystack
- Currency Conversion: Live exchange rates with no API key required
- Multi-Currency: Convert between any currencies instantly
🌍 Location & Geography #
- 194 Countries: Flags, dial codes, regions — complete offline dataset
- Three Data Sources: Built-in static list, restcountries.com v3.1, countrystatecity.in
- States & Cities: Complete location data for forms — offline lists for NG, US, GB, CA, AU, ZA, GH, KE
- Nigerian States: All 36 states + FCT available offline, no API needed
- Lazy Loading: Compile-time data materialised once on first access — O(1) lookups by code or dial code
- Source Switching: Same API call whether you're using static data or a live API
📡 Network & Connectivity #
- Network Monitoring: Real-time connection status
- Connection Quality: Measure latency and speed
- Ping Test: Check host connectivity
- Auto-Retry: Wait for connection with timeout
🎨 UI & Design #
- Responsive Helpers: Breakpoints, adaptive layouts
- Color Utilities: Generate palettes, calculate contrast
- Device Info: Platform detection, screen sizes
- Image Utils: Compression, picking, base64 encoding
🔐 Security & Storage #
- Encryption: AES, hashing (MD5, SHA-256), password hashing
- Secure Storage: Type-safe SharedPreferences wrapper
- Data Masking: Hide sensitive information
- Token Generation: API keys, OTP, UUID
📝 Logging & Debugging #
- Talker Integration: Beautiful console logs
- HTTP Logging: Track all API requests/responses
- Performance Monitoring: Log operation durations
- Custom Log Types: Navigation, user actions, errors
🗂️ Collections & Data #
- List Extensions: 40+ operations (groupBy, chunk, distinct)
- Map Extensions: Safe access, filtering, deep merge
- Result Type: Type-safe error handling
- Async State: Loading, success, error states
📁 File & Media #
- File Utilities: Read, write, copy, move operations
- Image Tools: Pick, compress, validate
- Path Helpers: Cross-platform path management
- Cleanup: Auto-delete old files
📦 Installation #
Add to your pubspec.yaml:
dependencies:
common_utils2: ^2.0.0
# Required for video features
video_player: ^2.8.2
chewie: ^1.7.5
# Required for notifications
firebase_messaging: ^14.7.9
flutter_local_notifications: ^16.3.0
# Required for downloads
dio: ^5.4.0
path_provider: ^2.1.1
permission_handler: ^11.2.0
# Required for caching
cached_network_image: ^3.3.1
flutter_cache_manager: ^3.3.1
Then run:
flutter pub get
flutter pub run build_runner build
🚀 Quick Start #
Initialize in your app #
import 'package:common_utils2/common_utils.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
// Initialize notifications
await CommonNotificationService.instance.initialize(
NotificationConfig.withDefaults(
onTokenRefreshed: (token) => myApi.updateFcmToken(token),
onNotificationTap: (payload) async {
myRouter.push(payload.deepLink!);
return true;
},
),
);
// Initialize notification store (for in-app notifications)
final notificationCubit = NotificationCubit();
await notificationCubit.initialize();
// Initialize core services
await StorageService.init();
await DeviceInfoHelper.init();
await LoggerService.init(
logLevel: LoggerLevel.debug,
enabled: true,
);
// Initialize network monitoring
await NetworkConnectivity.init(
onConnectivityChanged: (status) {
print('Connection: ${status.connectionType.name}');
},
);
// Initialize API-based utilities (optional)
BankUtils.init(paystackSecretKey: 'your_key');
// Initialize country utilities (optional — only needed for live API sources)
// Skip entirely if you only need offline static data.
CountryService.init(
source: CountrySource.restCountriesApi, // or .countryStateCityApi
httpClient: MyHttpClient(), // your package:http or dio client
cscApiKey: 'your-key', // only for countryStateCityApi
);
runApp(
MultiBlocProvider(
providers: [
BlocProvider.value(value: notificationCubit),
BlocProvider(create: (_) => DownloadCubit()),
],
child: MyApp(),
),
);
}
📚 Usage Examples #
Video Preloading (TikTok/Reels Style) #
// 1. Create your video item wrapper
class PostVideoItem extends VideoItem<PostModel> {
PostVideoItem(PostModel post)
: super(
id: post.id,
videoUrl: post.content.first.url,
thumbnailUrl: post.thumbNail.isNotEmpty ? post.thumbNail.first : null,
data: post,
);
@override
PostVideoItem copyWithData(PostModel newData) => PostVideoItem(newData);
}
// 2. Create the cubit with pagination
final videoItems = posts.map((p) => PostVideoItem(p)).toList();
final videoCubit = VideoPaginationCubit<PostModel>(
initialItems: videoItems,
fetchPage: _fetchPage, // Your API call
videoConfig: VideoPreloadConfig(
preloadAhead: 2,
keepBehind: 1,
maxConcurrentInits: 3,
),
paginationConfig: PaginationConfig(
fetchThreshold: 3, // Fetch when 3 items from end
pageSize: 10,
),
);
// 3. Use in PageView
PageView.builder(
onPageChanged: (index) {
videoCubit.onPageChanged(index); // Handles everything
},
itemCount: state.items.length,
itemBuilder: (context, index) {
return VideoPlayerWidget(item: state.items[index]);
},
)
// 4. Handle likes without reloading video
void _handleLike(PostModel post) {
// Update cache
cacheCubit.likePost(post.id, !post.liked);
// Update video cubit (keeps controller alive)
final updatedPost = post.copyWith(liked: !post.liked);
videoCubit.updateItemData(post.id, updatedPost);
// Fire API
bloc.add(LikePostEvent(id: post.id));
}
Download Manager #
// Download a video
final downloadId = await context.read<DownloadCubit>().addDownload(
url: 'https://example.com/video.mp4',
fileName: 'my_video.mp4',
type: DownloadType.video,
);
// Show downloads page
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DownloadsListPage()),
);
// Listen to download completion
BlocListener<DownloadCubit, DownloadState>(
listener: (context, state) {
if (state.completedDownloads.isNotEmpty) {
final latest = state.completedDownloads.last;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Downloaded: ${latest.fileName}')),
);
}
},
)
// Pause/Resume/Cancel
context.read<DownloadCubit>().pauseDownload(downloadId);
context.read<DownloadCubit>().resumeDownload(downloadId);
context.read<DownloadCubit>().cancelDownload(downloadId);
Push Notifications #
// Initialize with config
await CommonNotificationService.instance.initialize(
NotificationConfig.withDefaults(
androidIcon: '@drawable/ic_notification',
onTokenRefreshed: (token) => api.updateFcmToken(token),
onNotificationTap: (payload) async {
if (payload.deepLink != null) {
router.push(payload.deepLink!);
return true;
}
return false;
},
initialTopics: ['all_users'],
),
);
// Subscribe to topics after login
await CommonNotificationService.instance.subscribeMany([
'user_${userId}',
'vendors',
]);
// Show local notification
await CommonNotificationService.instance.show(
title: 'New Message',
body: 'You have a new message from John',
channelId: 'messages',
);
// On logout
await CommonNotificationService.instance.unsubscribeAll();
await CommonNotificationService.instance.deleteToken();
In-App Toast Notifications #
// Wrap your MaterialApp
MaterialApp.router(
builder: (context, child) => InAppNotificationOverlay(child: child!),
routerConfig: router,
)
// Show toast anywhere
InAppNotificationController.instance.show(
title: 'New Follower',
body: '@john started following you',
type: 'new_follower',
onTap: () => router.push('/profile/john'),
);
// Add badge to nav icon
NavigationDestination(
icon: NotificationBadge(
child: Icon(Icons.notifications_outlined),
),
label: 'Notifications',
)
// Show notification centre
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => NotificationCentrePage(
onEntryTap: (entry) {
if (entry.deepLink != null) router.push(entry.deepLink!);
},
),
),
);
Image Preloading #
// Preload images
await ImagePreloadService().preloadRange(
imageUrls,
context,
bufferSize: 5,
);
// Use cached image widget
CachedImageWidget(
imageUrl: 'https://example.com/image.jpg',
fit: BoxFit.cover,
)
Mixed Media Feeds (Facebook Style) #
// For feeds with mixed images and videos
final mediaItems = posts.map((p) => PostMediaItem(p)).toList();
final mediaCubit = MixedMediaCubit<PostModel>(items: mediaItems);
final videoCubit = LazyVideoCubit();
MultiBlocProvider(
providers: [
BlocProvider.value(value: mediaCubit),
BlocProvider.value(value: videoCubit),
],
child: MediaListView<PostModel>(
items: mediaItems,
overlayBuilder: (item) => _buildLikesCommentsUI(item),
onItemTap: (item) => _openPost(item),
),
)
String Extensions #
// Validation
'test@example.com'.isValidEmail; // true
'+2348012345678'.isValidNigerianPhone; // true
'1234567890123456'.isValidCreditCard; // true
// Capitalization
'hello world'.toTitleCase; // "Hello World"
'some_variable'.toCamelCase; // "someVariable"
// Masking
'john@example.com'.maskEmail; // "j***@example.com"
'+2348012345678'.maskPhone; // "******5678"
// Truncation
'Very long text here'.truncate(10); // "Very long..."
Validators #
// In TextFormField
TextFormField(
validator: Validators.emailValidator,
)
// Combine validators
TextFormField(
validator: Validators.combine([
Validators.required,
(value) => Validators.minLengthValidator(value, 8),
Validators.strongPasswordValidator,
]),
)
// Nigerian-specific
TextFormField(
validator: Validators.bvnValidator, // 11-digit BVN
)
Number Formatting #
// Currency
1500000.toCurrency(); // "₦1,500,000.00"
1234567.toCompactString(); // "1.2M"
// Percentages
0.75.toPercentage(); // "75%"
// File sizes
1048576.toBytesString(); // "1.00 MB"
// Calculations
100.percentOf(20); // 20.0
100.addPercentage(10); // 110.0
Bank Utilities #
// Get banks list
final banks = await BankUtils.getNigerianBanks();
// Validate account
final validation = await BankUtils.validateAccount(
accountNumber: '0123456789',
bankCode: '058', // GTBank
);
if (validation.isValid) {
print('Account Name: ${validation.accountName}');
}
// Search banks
final results = await BankUtils.searchBanks(
query: 'Access',
country: 'nigeria',
);
Currency Conversion #
// Convert currency
final conversion = await CurrencyUtils.convert(
amount: 100,
fromCurrency: 'USD',
toCurrency: 'NGN',
);
print(conversion.formattedConversion);
// Output: $100.00 = ₦76,500.00
// Get exchange rates
final rates = await CurrencyUtils.getExchangeRates(baseCurrency: 'USD');
print('1 USD = ${rates.getRate('NGN')} NGN');
// Convert to multiple currencies
final multi = await CurrencyUtils.convertToMultiple(
amount: 100,
fromCurrency: 'USD',
toCurrencies: ['NGN', 'GHS', 'EUR', 'GBP'],
);
Country & Location #
// ── Offline static data — instant, no setup needed ──────────────────────────
// Full country list (194 countries, lazy-loaded, sorted A→Z)
final countries = CountryData.all;
// O(1) lookup by ISO code
final nigeria = CountryData.byCode['NG'];
// Lookup by dial code
final plus234 = CountryData.byDialCode['+234']; // [Nigeria]
final plus1 = CountryData.byDialCode['+1']; // [United States, Canada]
// Search across name, code, and dial code
final hits = CountryData.search('ghana', limit: 5);
// Popular countries first (great for pickers)
final list = CountryData.withPopularFirst(CountryData.all);
// Filter by region
final africa = CountryData.byRegion('Africa');
// Nigerian states — always offline, no API needed
final states = await CountryService.instance.getStates('NG');
if (states.success) {
for (final s in states.states) { print(s.name); }
}
// ── Live API — richer data, source-switchable ────────────────────────────────
// Configure once in main() — restcountries.com (free, no key)
CountryService.init(
source: CountrySource.restCountriesApi,
httpClient: MyHttpClient(), // your package:http or dio wrapper
);
// Or with CountryStateCity API (states + cities for any country)
CountryService.init(
source: CountrySource.countryStateCityApi,
cscApiKey: 'your-free-key',
httpClient: MyHttpClient(),
);
// Use anywhere — same API regardless of source
final result = await CountryService.instance.getAllCountries();
if (result.success) {
for (final c in result.countries) {
print('${c.flag} ${c.name} (${c.dialCode})');
}
}
// Single country
final ng = await CountryService.instance.getCountryByCode('NG');
print('Capital: ${ng?.capital}'); // only populated from API sources
// States — offline for NG, US, GB, CA, AU, ZA, GH, KE; live for everything else
final states = await CountryService.instance.getStates('BR'); // live fetch
// Cities — requires countryStateCityApi
final cities = await CountryService.instance.getCities(
countryCode: 'NG',
stateCode: 'LA', // Lagos
);
// Dial codes — always instant, from static data
final dialCodes = CountryService.instance.getDialCodes();
// Popular countries — always instant
final popular = CountryService.instance.popularCountries;
Inject your HTTP client
// package:http
import 'dart:convert';
import 'package:http/http.dart' as http;
class HttpClient implements CountryHttpClient {
@override
Future<dynamic> get(String url, {Map<String, String>? headers}) async {
final res = await http.get(Uri.parse(url), headers: headers);
if (res.statusCode != 200) {
throw CountryServiceException('HTTP ${res.statusCode}: $url');
}
return jsonDecode(res.body);
}
}
// package:dio
class DioClient implements CountryHttpClient {
final _dio = Dio();
@override
Future<dynamic> get(String url, {Map<String, String>? headers}) async {
final res = await _dio.get<dynamic>(
url,
options: Options(headers: headers),
);
return res.data;
}
}
Network Connectivity #
// Check connection status
final status = await NetworkConnectivity.checkConnectivity();
print('Connected: ${status.isConnected}');
print('Type: ${status.connectionType.name}');
// Listen to changes
NetworkConnectivity.onConnectivityChanged.listen((status) {
if (status.isConnected) {
print('✓ Back online!');
} else {
print('✗ Connection lost');
}
});
// Check connection quality
final quality = await NetworkConnectivity.getConnectionQuality();
print('Quality: ${quality.icon} ${quality.name}');
// Ping test
final ping = await NetworkConnectivity.ping(host: 'google.com');
print('Latency: ${ping.latencyMs}ms');
// Quick checks
if (NetworkConnectivity.isWifi) {
print('Connected via WiFi');
}
🔧 Configuration #
FCM Setup (Android) #
Add to android/app/build.gradle:
apply plugin: 'com.google.gms.google-services'
Add to android/build.gradle:
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}
Add google-services.json to android/app/
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
FCM Setup (iOS) #
- Add
GoogleService-Info.plisttoios/Runner/ - Enable Push Notifications capability in Xcode
- Enable Background Modes → Remote notifications
- Upload APNs certificate to Firebase Console
🎯 Nigerian-Specific Features #
- ✅ Nigerian phone number validation
- ✅ BVN (Bank Verification Number) validation
- ✅ NIN (National ID) validation
- ✅ Nigerian banks list (Access, GTBank, Zenith, etc.)
- ✅ Account number verification
- ✅ Nigerian states (all 36 + FCT) — offline, no API needed
- ✅ Naira (₦) currency formatting
- ✅ Nigerian bank account format (10 digits)
🤝 Contributing #
Contributions are welcome!
📄 License #
This project is licensed under the MIT License - see the LICENSE file for details.
🐛 Issues #
Found a bug? Please open an issue.
⭐ Support #
If you find this package helpful, please give it a star on GitHub!
📮 Contact #
- Author: Diwe Innocent
- Email: diweesomchi@gmail.com
- Website: https://innocentdiwe.qzz.io
Made with ❤️ in Nigeria 🇳🇬