421 lines
No EOL
12 KiB
Markdown
421 lines
No EOL
12 KiB
Markdown
# browser_tab_manager
|
|
## Getting Started
|
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
- [online documentation](https://docs.flutter.dev/)
|
|
# 🧠 Browser Tab Manager - Complete Code Study Guide
|
|
|
|
## 📋 Table of Contents
|
|
1. [App Architecture Overview](#architecture)
|
|
2. [Entry Point - main.dart](#main-dart)
|
|
3. [Data Layer - Models](#models)
|
|
4. [Service Layer](#services)
|
|
5. [UI Layer - Screens](#screens)
|
|
6. [Component Layer - Widgets](#widgets)
|
|
7. [Utilities & Constants](#utilities)
|
|
8. [Data Flow & State Management](#data-flow)
|
|
9. [Browser API Integration](#browser-api)
|
|
10. [Extension Communication](#extension-comm)
|
|
|
|
---
|
|
|
|
## 🏗️ App Architecture Overview {#architecture}
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ 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 {#main-dart}
|
|
|
|
```dart
|
|
void main() {
|
|
runApp(const BrowserTabManagerApp());
|
|
}
|
|
```
|
|
|
|
**WHAT HAPPENS HERE:**
|
|
1. `main()` is the entry point of every Dart/Flutter app
|
|
2. `runApp()` tells Flutter to start the app with our root widget
|
|
3. Creates the widget tree and starts the rendering engine
|
|
|
|
```dart
|
|
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 state
|
|
- `MaterialApp`: Root widget that provides Material Design theming
|
|
- `theme` & `darkTheme`: Define app-wide visual styling
|
|
- `home`: The first screen users see (TabManagerHome)
|
|
|
|
---
|
|
|
|
## 📊 Data Layer - Models {#models}
|
|
|
|
### TabData Model (`models/tab_data.dart`)
|
|
|
|
```dart
|
|
class TabData {
|
|
String id; // Unique identifier
|
|
String title; // Display name
|
|
String url; // Web address
|
|
String favicon; // Icon URL
|
|
DateTime lastAccessed; // When last used
|
|
bool isPinned; // Pinned status
|
|
String type; // 'tab', 'bookmark', or 'history'
|
|
int? visitCount; // Number of visits (nullable)
|
|
String? folder; // Bookmark folder (nullable)
|
|
}
|
|
```
|
|
|
|
**PURPOSE:**
|
|
- **Data Structure**: Represents all types of browser items uniformly
|
|
- **Type Safety**: Dart ensures correct data types
|
|
- **Nullable Fields**: `?` means the field can be null
|
|
|
|
### Factory Constructor:
|
|
```dart
|
|
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 {#services}
|
|
|
|
### BrowserApiService (`services/browser_api_service.dart`)
|
|
|
|
```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 operations
|
|
- `Future<T>`: Represents a value that will be available later
|
|
- **Abstraction**: Hides complex browser API details
|
|
|
|
### ExtensionService (`services/extension_service.dart`)
|
|
|
|
```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 {#screens}
|
|
|
|
### TabManagerHome (`screens/tab_manager_home.dart`)
|
|
|
|
This is the **BRAIN** of the application - it manages all state and coordinates everything.
|
|
|
|
#### State Variables:
|
|
```dart
|
|
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:
|
|
```dart
|
|
@override
|
|
void initState() {
|
|
super.initState(); // Call parent setup
|
|
_setupExtensionService(); // Start listening for extension
|
|
_loadAllData(); // Load browser data
|
|
searchController.addListener(_filterItems); // Watch search input
|
|
}
|
|
```
|
|
|
|
**setState()** - The magic that updates UI:
|
|
```dart
|
|
setState(() {
|
|
allItems = newData; // Change state
|
|
_filterItems(); // Update filtered view
|
|
});
|
|
// Flutter automatically rebuilds UI after setState finishes
|
|
```
|
|
|
|
---
|
|
|
|
## 🧩 Component Layer - Widgets {#widgets}
|
|
|
|
### ItemCard (`widgets/item_card.dart`)
|
|
|
|
**STATELESS WIDGET** - Displays data, doesn't manage state:
|
|
|
|
```dart
|
|
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:**
|
|
1. Parent passes data and callback functions
|
|
2. Widget displays the data
|
|
3. User interaction calls parent's functions
|
|
4. Parent updates state and rebuilds widget with new data
|
|
|
|
### SearchBar (`widgets/search_bar.dart`)
|
|
|
|
```dart
|
|
TextField(
|
|
controller: controller, // Links to parent's TextEditingController
|
|
onChanged: (text) => { // Calls parent when text changes
|
|
// Parent handles the search logic
|
|
},
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ Utilities & Constants {#utilities}
|
|
|
|
### Helpers (`utils/helpers.dart`)
|
|
|
|
```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`)
|
|
|
|
```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 {#data-flow}
|
|
|
|
### 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
|
|
|
|
```dart
|
|
// 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 {#browser-api}
|
|
|
|
### How Browser APIs Work:
|
|
|
|
```dart
|
|
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:**
|
|
1. **Check Availability**: Is the browser API injected?
|
|
2. **Get Reference**: Access the API object
|
|
3. **Call Method**: Execute with parameters
|
|
4. **Handle Response**: Convert to usable format
|
|
|
|
---
|
|
|
|
## 📡 Extension Communication {#extension-comm}
|
|
|
|
### Message Passing System:
|
|
|
|
```dart
|
|
// 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 monitoring
|
|
- `stopTracking`: Stop tab monitoring
|
|
- `updateTabs`: Extension sends current tabs
|
|
- `getStatus`: 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/await` for non-blocking operations
|
|
- `Future<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
|
|
|
|
1. **Trace a complete user action** from UI tap to state update
|
|
2. **Follow data transformation** from browser API to UI display
|
|
3. **Understand lifecycle methods** and when they're called
|
|
4. **Practice modifying** one small feature at a time
|
|
5. **Add logging** to see the flow in action
|
|
|
|
This architecture provides a solid foundation for building complex, maintainable Flutter web applications! 🚀 |