A modular mobile dashboard application demonstrating dynamic widget loading, shared state management, and offline-first architecture.
- Overview
- Architecture
- Features
- Framework & Libraries
- State Management
- Offline Handling
- CI/CD Strategy
- Getting Started
- Adding New Widgets
- Project Structure
Nexus Dashboard is a production-ready modular mobile application built with Flutter that showcases enterprise-level architecture patterns. The application serves as a personal systems dashboard where independent widgets (Notes, AI Chat, Analytics) can be dynamically loaded, managed, and updated without affecting the core application shell.
- Dynamic Module Loading: Widgets register themselves and load at runtime
- Offline-First Architecture: Full functionality without network connectivity
- Clean Architecture: Clear separation of concerns across layers
- State Management: BLoC pattern for predictable state handling
- Synchronization: Smart queue-based sync with conflict resolution
- Error Resilience: Module isolation prevents cascading failures
Nexus Dashboard follows Clean Architecture principles with a Modular Monolith approach, enabling independent feature development while maintaining a single deployable unit.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Notes β β AI Chat β β Analytics β β
β β Module β β Module β β Module β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β ββββββββββββββββββ΄ββββββββββββββββββ β
β β β
β ββββββββΌβββββββ β
β β Dashboard β β
β β Shell β β
β ββββββββ¬βββββββ β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Domain Layer β
β ββββββββββββββββββ΄βββββββββββββββββ β
β β Business Logic & Entities β β
β β Repository Interfaces β β
β ββββββββββββββββββ¬βββββββββββββββββ β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ€
β Data Layer β
β βββββββββββββββββββββββΌββββββββββββββββββββββ β
β β β β β
β ββββΌβββββ βββββββΌββββββ ββββββΌββββββ β
β β Local β β Sync β β Remote β β
β βStorageβ β Manager β β API β β
β β(Hive) β β β β(Future) β β
β βββββββββ βββββββββββββ ββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Each feature module is self-contained with its own:
- Domain Layer: Entities and repository interfaces
- Data Layer: Models and repository implementations
- Presentation Layer: BLoC, UI pages, and module registration
Module Structure (e.g., Notes)
βββ domain/
β βββ entities/ # Business objects
β βββ repositories/ # Abstract contracts
βββ data/
β βββ models/ # Data transfer objects
β βββ repositories/ # Concrete implementations
βββ presentation/
βββ bloc/ # State management
βββ pages/ # UI screens
βββ [feature]_module.dart # Module registration
User Action β BLoC Event β Repository β Data Source
β β
βββββββββ BLoC State β Repository βββββββ
- Widgets implement
IModuleinterface - Runtime discovery via
ModuleRegistry - Hot-swappable without app restart
- Create, read, and delete notes
- Offline-first with sync option
- Timestamp tracking
- Mock conversational AI interface
- Message history persistence
- Clear chat functionality
- Dashboard statistics overview
- Activity visualization
- Storage usage tracking
- Last sync information
- Full CRUD operations without network
- Queue-based sync with retry logic
- Conflict resolution strategies
- Module-level error boundaries
- Graceful degradation
- User-friendly error messages
- Retry mechanisms
Why Flutter?
- Cross-platform efficiency: Single codebase for iOS and Android
- Native performance: Compiles to ARM machine code
- Rich ecosystem: Extensive plugin library
- Hot reload: Rapid development iteration
- Material Design: Beautiful, consistent UI out-of-the-box
- Strong typing: Dart's null safety prevents runtime errors
| Library | Version | Purpose | Reasoning |
|---|---|---|---|
flutter_bloc |
^8.1.3 | State management | Predictable, testable state with clear separation of concerns |
get_it |
^7.6.4 | Dependency injection | Loose coupling and easy testing |
hive |
^2.2.3 | Local database | Fast, lightweight NoSQL storage for offline data |
connectivity_plus |
^5.0.1 | Network detection | Monitor connectivity for sync operations |
equatable |
^2.0.5 | Value equality | Simplify state comparisons in BLoC |
uuid |
^4.1.0 | Unique IDs | Generate collision-free identifiers |
intl |
^0.18.1 | Internationalization | Date formatting and localization support |
State Management: BLoC Pattern
βββ flutter_bloc: State management framework
βββ equatable: Value equality for states/events
βββ Provider: (via BLoC) Widget tree injection
Dependency Injection: Service Locator
βββ get_it: Global service registry
Local Storage: Hive
βββ hive: Core NoSQL database
βββ hive_flutter: Flutter-specific initialization
Network: Connectivity Monitoring
βββ connectivity_plus: Real-time network statusNexus Dashboard uses the BLoC (Business Logic Component) pattern for state management, providing:
- Clear separation between UI and business logic
- Predictable state changes via events
- Easy testing with mockable dependencies
- Scalable architecture for complex apps
βββββββββββββββ
β UI β Dispatches events
β (Widget) ββββββββββββββββββββββ
βββββββββββββββ β
β βΌ
β βββββββββββββββ
β Rebuilds on β BLoC β
β state changes β (Business β
β β Logic) β
β βββββββββββββββ
β β
β β Calls methods
β βΌ
β βββββββββββββββ
β β Repository β
β β (Data Layer)β
β βββββββββββββββ
β β
β βΌ
β βββββββββββββββ
ββββββββββββββββββββββData Sources β
β(Hive, API) β
βββββββββββββββ
DashboardBloc
βββ Manages module navigation
βββ Tracks active modules
βββ Coordinates module lifecycleNotesBloc, ChatBloc, AnalyticsBloc
βββ Independent state per feature
βββ No cross-module dependencies
βββ Isolated state updatesModules communicate through:
- Shared Repository Layer: Common data access
- Event Broadcasting: Via BLoC streams
- Dashboard Coordinator: Orchestrates cross-module actions
// Example: Analytics reads from Notes/Chat repositories
class AnalyticsBloc {
final NoteRepository _noteRepo;
final ChatRepository _chatRepo;
// Aggregates data from multiple sources
Future<void> _calculateStats() async {
final notes = await _noteRepo.getNotes();
final messages = await _chatRepo.getMessages();
// Process statistics...
}
}Nexus Dashboard implements a comprehensive offline-first architecture ensuring full functionality without network connectivity.
Why Hive?
- Zero native dependencies
- Fast read/write operations
- Type-safe storage
- Minimal storage footprint
- Built-in encryption support
Storage Structure:
Hive Boxes
βββ notes_box: All note documents
βββ messages_box: Chat message history
βββ analytics_box: Cached statistics
βββ sync_queue_box: Pending sync operations
Cache-First Approach:
Read Operation Flow:
1. Check local cache (Hive)
2. Return cached data if available
3. Fetch from remote if cache miss
4. Update cache with remote data
5. Return data to UI
Write Operation Flow:
1. Update local cache immediately (Optimistic Update)
2. Add to sync queue
3. Notify UI of local change
4. Sync to remote when online
5. Resolve conflicts if any
Sync Manager Architecture:
SyncManager Components:
βββ Connectivity Monitor: Detects network state changes
βββ Sync Queue: Stores pending operations
βββ Retry Logic: Exponential backoff for failed syncs
βββ Conflict Resolver: Last-write-wins strategySync Queue Structure:
{
"type": "save_note",
"data": { /* operation data */ },
"timestamp": "2025-11-09T10:30:00Z",
"retries": 0,
"maxRetries": 3
}Synchronization Flow:
[Offline] User creates note
β
Save to local Hive storage
β
Add to sync queue
β
Show success to user (optimistic UI)
β
[Network restored]
β
Sync Manager detects connectivity
β
Process sync queue (FIFO)
β
For each queued item:
ββ Attempt sync to remote
ββ On success: Remove from queue
ββ On failure: Increment retry count
ββ If retries < max: Keep in queue
ββ If retries β₯ max: Log error, remove
Strategy: Last-Write-Wins (LWW)
Conflict Detection:
1. Compare timestamps: local vs remote
2. If remote is newer:
ββ Overwrite local with remote data
3. If local is newer:
ββ Push local changes to remote
4. If timestamps equal:
ββ Use remote as source of truth- Eventual Consistency: All nodes converge to same state
- Local-First: Users never blocked by network issues
- Transparent Sync: Background synchronization
- User Control: Manual sync trigger available
Nexus Dashboard implements a comprehensive CI/CD pipeline using GitHub Actions for automated building
Code Push β GitHub Actions Trigger
β
βββββββββββββββββββββββββ
β Build & Test Stage β
β - Lint code β
βββββββββββββββββββββββββ
β
βββββββββββββββββββββββββ
β Build Stage β
β - Build APK/AAB β
β - Build iOS IPA β
β - Sign artifacts β
βββββββββββββββββββββββββ
β
βββββββββββββββββββββββββ
β Notification Stage β
β - Email reports β
βββββββββββββββββββββββββ
Triggers:
- Push to
mainordevelopbranch - Pull request creation
- Manual workflow dispatch
Jobs:
- Code linting with
flutter analyze
# Required
Flutter SDK: >=3.0.0
Dart SDK: >=3.0.0
# Recommended
Android Studio / VS Code
Xcode (for iOS development)
Git# 1. Clone the repository
git clone https://github.com/droidchief/Nexsus-Dashboard
cd nexus_dashboard
# 2. Install dependencies
flutter pub get
# 3. Run the app
flutter run
# Optional: Run tests
flutter test
# Optional: Check for issues
flutter doctor# No additional setup required
flutter run -d androidcd ios
pod install
cd ..
flutter run -d iphoneFollow these steps to add a new widget module to Nexus Dashboard:
lib/features/[your_widget]/
βββ domain/
β βββ entities/
β β βββ [entity].dart
β βββ repositories/
β βββ [repository].dart
βββ data/
β βββ models/
β β βββ [model].dart
β βββ repositories/
β βββ [repository]_impl.dart
βββ presentation/
βββ bloc/
β βββ [widget]_bloc.dart
β βββ [widget]_event.dart
β βββ [widget]_state.dart
βββ pages/
β βββ [widget]_page.dart
βββ [widget]_module.dart// lib/features/your_widget/presentation/your_widget_module.dart
import 'package:flutter/material.dart';
import '../../../core/interfaces/i_module.dart';
class YourWidgetModule implements IModule {
@override
String get id => 'your_widget';
@override
String get title => 'Your Widget';
@override
IconData get icon => Icons.your_icon;
@override
Color get color => const Color(0xFFYOURCOLOR);
@override
String getRoute() => '/your_widget';
@override
int get priority => 4; // Display order
@override
Widget getWidget() {
return BlocProvider(
create: (_) => getIt<YourWidgetBloc>()..add(LoadData()),
child: const YourWidgetPage(),
);
}
}// lib/features/your_widget/domain/entities/your_entity.dart
import 'package:equatable/equatable.dart';
class YourEntity extends Equatable {
final String id;
final String name;
// Add your fields
const YourEntity({
required this.id,
required this.name,
});
@override
List<Object?> get props => [id, name];
}
// lib/features/your_widget/domain/repositories/your_repository.dart
import '../entities/your_entity.dart';
abstract class YourRepository {
Future<List<YourEntity>> getItems();
Future<void> saveItem(YourEntity item);
Future<void> deleteItem(String id);
}// lib/features/your_widget/data/models/your_model.dart
import '../../domain/entities/your_entity.dart';
class YourModel extends YourEntity {
const YourModel({
required super.id,
required super.name,
});
factory YourModel.fromJson(Map<String, dynamic> json) {
return YourModel(
id: json['id'] as String,
name: json['name'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
};
}
}
// lib/features/your_widget/data/repositories/your_repository_impl.dart
import '../../../../core/storage/local_storage.dart';
import '../../../../core/storage/sync_manager.dart';
import '../../domain/entities/your_entity.dart';
import '../../domain/repositories/your_repository.dart';
import '../models/your_model.dart';
class YourRepositoryImpl implements YourRepository {
final LocalStorage _storage;
final SyncManager _syncManager;
YourRepositoryImpl(this._storage, this._syncManager);
@override
Future<List<YourEntity>> getItems() async {
// Implement storage logic
}
@override
Future<void> saveItem(YourEntity item) async {
// Save to local storage
// Queue for sync
await _syncManager.queueAction('save_item', data);
}
@override
Future<void> deleteItem(String id) async {
// Implement deletion logic
}
}// lib/features/your_widget/presentation/bloc/your_widget_event.dart
abstract class YourWidgetEvent extends Equatable {
const YourWidgetEvent();
@override
List<Object?> get props => [];
}
class LoadData extends YourWidgetEvent {}
// lib/features/your_widget/presentation/bloc/your_widget_state.dart
abstract class YourWidgetState extends Equatable {
const YourWidgetState();
@override
List<Object?> get props => [];
}
class YourWidgetInitial extends YourWidgetState {}
class YourWidgetLoaded extends YourWidgetState {
final List<YourEntity> items;
const YourWidgetLoaded(this.items);
@override
List<Object?> get props => [items];
}
// lib/features/your_widget/presentation/bloc/your_widget_bloc.dart
class YourWidgetBloc extends Bloc<YourWidgetEvent, YourWidgetState> {
final YourRepository _repository;
YourWidgetBloc(this._repository) : super(YourWidgetInitial()) {
on<LoadData>(_onLoadData);
}
Future<void> _onLoadData(LoadData event, Emitter emit) async {
try {
final items = await _repository.getItems();
emit(YourWidgetLoaded(items));
} catch (e) {
// Handle error
}
}
}// lib/features/your_widget/presentation/pages/your_widget_page.dart
class YourWidgetPage extends StatelessWidget {
const YourWidgetPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Your Widget')),
body: BlocBuilder<YourWidgetBloc, YourWidgetState>(
builder: (context, state) {
if (state is YourWidgetLoaded) {
return ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(state.items[index].name),
);
},
);
}
return const CircularProgressIndicator();
},
),
);
}
}// lib/core/di/injection.dart
Future<void> setupDependencies() async {
// ... existing code ...
// Add your repository
getIt.registerLazySingleton<YourRepository>(
() => YourRepositoryImpl(getIt(), getIt()),
);
// Add your BLoC
getIt.registerFactory(() => YourWidgetBloc(getIt()));
}// lib/features/dashboard/data/module_registry.dart
void registerModules() {
_modules.clear();
_modules.addAll([
NotesModule(),
ChatModule(),
AnalyticsModule(),
YourWidgetModule(), // Add your module here
]);
_modules.sort((a, b) => a.priority.compareTo(b.priority));
}# Hot reload to see your new widget
# It should appear on the dashboard automatically!
flutter runnexus_dashboard/
βββ lib/
β βββ main.dart # App entry point
β βββ core/
β β βββ di/
β β β βββ injection.dart # Dependency injection setup
β β βββ interfaces/
β β β βββ i_module.dart # Module interface
β β βββ storage/
β β β βββ local_storage.dart # Hive wrapper
β β β βββ sync_manager.dart # Sync logic
β β βββ theme/
β β β βββ app_theme.dart # Theme configuration
β β β βββ app_typography.dart # Typography styles
β β βββ widgets/
β β βββ error_boundary.dart # Error handling
β βββ features/
β βββ dashboard/
β β βββ data/
β β β βββ module_registry.dart # Module registration
β β βββ presentation/
β β βββ bloc/ # Dashboard state
β β βββ pages/ # Dashboard UI
β βββ notes/ # Notes module
β βββ chat/ # Chat module
β βββ analytics/ # Analytics module
βββ test/ # Unit tests
βββ integration_test/ # Integration tests
βββ android/ # Android native code
βββ ios/ # iOS native code
βββ .github/
β βββ workflows/
β βββ ci_cd.yml # CI/CD pipeline
βββ pubspec.yaml # Dependencies
- Lazy Loading: Modules loaded on-demand
- Const Constructors: Minimize widget rebuilds
- ListView.builder: Efficient list rendering
- Image Caching: Reduced memory footprint
- State Optimization: Only rebuild affected widgets
Victor Loveday
Senior Mobile Developer
GitHub β’ LinkedIn
- Flutter team for the amazing framework
- BLoC library maintainers
- Open source community
Built with β€οΈ using Flutter