12 KiB
Main Commands
flutter run -d chrome
flutter run -d web-server --web-port=8080
flutter devices
flutter run -d chrome --release
flutter run -d chrome --debug
flutter build web
flutter clean flutter pub get flutter run -d chrome
browser_tab_manager
Extension
https://drive.google.com/drive/folders/1pAniw90J-cWrH7nMVaxY0pl0TMG63AJA?usp=sharing
Add this folder in developer mode: Go To: chrome://extensions/
To manage your Chrome extensions
- Open Chrome browser
- Type chrome://extensions/ in the address bar
- Or click the three dots menu > Extensions > Manage Extensions'
Start Developer mode: In right corner. Load Unpacked: Add entire folder in left corner.
Start Extension in right corner header after the search bar.
Getting Started
📋 Table of Contents
- App Architecture Overview
- Entry Point - main.dart
- Data Layer - Models
- Service Layer
- UI Layer - Screens
- Component Layer - Widgets
- Utilities & Constants
- Data Flow & State Management
- Browser API Integration
- Extension Communication
🏗️ App Architecture Overview
┌─────────────────────────────────────────┐
│ main.dart │
│ (App Entry Point) │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ TabManagerHome │
│ (Main Screen State) │
└─────┬─────────────────────────────┬─────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Services │ │ Widgets │
│ Layer │ │ Layer │
└─────┬─────┘ └─────┬─────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Models │ │ Utils & │
│ Layer │ │ Constants │
└───────────┘ └───────────┘
Key Concepts:
- Separation of Concerns: Each layer has specific responsibilities
- Unidirectional Data Flow: Data flows down, events flow up
- State Management: Centralized in TabManagerHome using setState()
- Service Communication: Browser APIs and Extension messaging
🚀 Entry Point - main.dart
void main() {
runApp(const BrowserTabManagerApp());
}
main()is the entry point of every Dart/Flutter apprunApp()tells Flutter to start the app with our root widget- Creates the widget tree and starts the rendering engine
class BrowserTabManagerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Browser Tab Manager',
theme: ThemeData(...),
home: const TabManagerHome(),
);
}
}
BREAKDOWN:
StatelessWidget: Never changes its appearance based on internal stateMaterialApp: Root widget that provides Material Design themingtheme&darkTheme: Define app-wide visual stylinghome: The first screen users see (TabManagerHome)
📊 Data Layer - Models
TabData Model (models/tab_data.dart)
class TabData {
String id;
String title;
String url;
String favicon;
DateTime lastAccessed; // When last used
bool isPinned;
String type; // 'tab', 'bookmark', or 'history'
int? visitCount; // (nullable)
String? folder; // Bookmark folder (nullable)
}
PURPOSE:
- Data Structure: Represents all types of browser items uniformly
- Type Safety: Dart ensures correct data types
Factory Constructor:
factory TabData.fromJson(Map<String, dynamic> json) => TabData(
id: json['id'].toString(),
title: json['title'] ?? 'Untitled', // ?? means "if null, use default"
// ... more fields
);
WHAT IT DOES:
- Converts JSON data from browser APIs into TabData objects
- Handles missing/null values gracefully with defaults
- Standardizes different API response formats
🔧 Service Layer
BrowserApiService (services/browser_api_service.dart)
class BrowserApiService {
Future<List<TabData>> getTabs() async {
final result = await _callBrowserAPI('getTabs');
// Convert raw data to TabData objects
}
}
KEY CONCEPTS:
async/await: Handles asynchronous operationsFuture<T>: Represents a value that will be available later- Abstraction: Hides complex browser API details
ExtensionService (services/extension_service.dart)
void setupListener() {
html.window.onMessage.listen((event) => {
// Handle messages from browser extension
});
}
PURPOSE:
- Communication Bridge: Between web app and browser extension
- Event-Driven: Responds to messages from extension
- Callback Pattern: Uses function pointers for responses
🎨 UI Layer - Screens
TabManagerHome (screens/tab_manager_home.dart)
This is the BRAIN of the application - it manages all state and coordinates everything.
State Variables:
class _TabManagerHomeState extends State<TabManagerHome> {
List<TabData> allItems = []; // ALL data from browser
List<TabData> filteredItems = []; // DISPLAYED data (after search/filter)
bool isGridView = true; // View mode toggle
String sortBy = 'recent'; // Current sort method
String filterType = 'all'; // Current filter
bool isLoading = true; // Loading spinner state
bool extensionMode = false; // Extension tracking mode
}
Key Methods:
initState() - Runs when widget is created:
@override
void initState() {
super.initState(); // Call parent setup
_setupExtensionService(); // Start listening for extension
_loadAllData(); // Load browser data
searchController.addListener(_filterItems); // Watch search input
}
setState() updates UI:
setState(() {
allItems = newData; // Change state
_filterItems(); // Update filtered view
});
// Flutter automatically rebuilds UI after setState finishes
🧩 Component Layer - Widgets
ItemCard (widgets/item_card.dart)
STATELESS WIDGET - Displays data, doesn't manage state:
class ItemCard extends StatelessWidget {
final TabData item; // Data from parent
final VoidCallback onTap; // Function to call when tapped
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onTap, // Call parent's function
child: // ... UI layout
),
);
}
}
DATA FLOW:
- Parent passes data and callback functions
- Widget displays the data
- User interaction calls parent's functions
- Parent updates state and rebuilds widget with new data
SearchBar (widgets/search_bar.dart)
TextField(
controller: controller, // Links to parent's TextEditingController
onChanged: (text) => { // Calls parent when text changes
// Parent handles the search logic
},
)
🛠️ Utilities & Constants
Helpers (utils/helpers.dart)
static List<TabData> filterItems(
List<TabData> items,
String query,
String filterType,
) {
return items.where((item) => {
// Filter logic here
}).toList();
}
PURPOSE:
- Pure Functions: Same input always produces same output
- Reusable Logic: Can be used anywhere in the app
- Testing: Easy to unit test
Constants (constants/app_constants.dart)
class AppConstants {
static const Color primaryColor = Color(0xFF0175C2);
static const String extensionSource = 'tab-tracker-extension';
}
BENEFITS:
- Single Source of Truth: Change value in one place
- Type Safety: Compile-time checking
- Maintainability: Easy to update app-wide settings
🔄 Data Flow & State Management
Complete Data Flow Cycle:
1. User Action (tap, type, click)
↓
2. Widget calls parent function
↓
3. Parent updates state with setState()
↓
4. Flutter rebuilds widget tree
↓
5. UI reflects new state
Example: Search Flow
// 1. User types in search bar
SearchBar(onChanged: (text) => {
// 2. SearchBar calls parent's function
_filterItems();
});
// 3. Parent filters data and updates state
void _filterItems() {
setState(() => {
filteredItems = //filter logic
});
// 4. Flutter rebuilds UI with new filteredItems
}
🌐 Browser API Integration
How Browser APIs Work:
Future<String?> _callBrowserAPI(String method, [List<dynamic>? args]) async {
// 1. Check if browser API exists
if (!js_util.hasProperty(html.window, 'BrowserAPI')) {
return null; // Development mode
}
// 2. Get the API object from browser
final browserAPI = js_util.getProperty(html.window, 'BrowserAPI');
// 3. Call the specific method
final result = await js_util.promiseToFuture(
js_util.callMethod(function, 'call', [browserAPI, ...args])
);
return json.encode(result); // 4. Return as JSON string
}
STEPS:
- Check Availability: Is the browser API injected?
- Get Reference: Access the API object
- Call Method: Execute with parameters
- Handle Response: Convert to usable format
📡 Extension Communication
Message Passing System:
// SENDING to extension:
html.window.postMessage({
'source': 'tab-tracker-webapp',
'action': 'startTracking'
}, '*');
// RECEIVING from extension:
html.window.onMessage.listen((event) => {
final data = event.data;
if (data['source'] == 'tab-tracker-extension') {
_handleExtensionMessage(data);
}
});
COMMUNICATION FLOW:
Web App ←→ Browser Window ←→ Extension
Message Types:
startTracking: Begin tab monitoringstopTracking: Stop tab monitoringupdateTabs: Extension sends current tabsgetStatus: Request current state
🎯 Key Learning Points
1. State Management Pattern
- State lives in parent components
- Children receive data and callback functions
- setState() triggers UI rebuilds
2. Async Programming
async/awaitfor non-blocking operationsFuture<T>represents eventual values- Error handling with try/catch
3. Widget Communication
- Parent-to-child: Pass data via constructor
- Child-to-parent: Pass callback functions
- Sibling-to-sibling: Through common parent
4. Service Layer Benefits
- Separates business logic from UI
- Makes testing easier
- Provides clean abstractions
5. Browser Integration
- JavaScript interop for browser APIs
- Message passing for extension communication
- Graceful degradation for development mode
🔍 Next Steps for Deep Learning
- Trace a complete user action from UI tap to state update
- Follow data transformation from browser API to UI display
- Understand lifecycle methods and when they're called
- Practice modifying one small feature at a time
- Add logging to see the flow in action
This architecture provides a solid foundation for building complex, maintainable Flutter web applications! 🚀