0% found this document useful (0 votes)
1 views22 pages

Flutter Web Dashboard App

This document provides a comprehensive guide to building a web dashboard app using Flutter and Dart with Clean Architecture. It outlines the project structure, including the separation of concerns into Presentation, Domain, and Data layers, and provides examples of entities, use cases, repositories, and UI components. Additionally, it emphasizes the importance of state management, error handling, and clean code principles for maintainability and scalability.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views22 pages

Flutter Web Dashboard App

This document provides a comprehensive guide to building a web dashboard app using Flutter and Dart with Clean Architecture. It outlines the project structure, including the separation of concerns into Presentation, Domain, and Data layers, and provides examples of entities, use cases, repositories, and UI components. Additionally, it emphasizes the importance of state management, error handling, and clean code principles for maintainability and scalability.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 22

To create a web dashboard app using Flutter and Dart with Clean

Architecture, state management, navigation, collections, class methods, and


attributes, you need to structure your code properly to ensure scalability,
maintainability, and separation of concerns. Here's a step-by-step guide on
how you can approach building such an app.

Project Structure
First, let's establish a Clean Architecture structure. We'll separate concerns
into multiple layers: Presentation, Domain, and Data.
Folder Structure
bash
Copy code
lib/
├── core/ # Core utilities and shared code
│ ├── error/ # Error handling and exceptions
│ ├── network/ # API calls, network-related classes
│ └── utils/ # Shared helpers and utilities

├── features/ # Feature-based structure
│ ├── dashboard/ # Dashboard-related functionality
│ │ ├── data/ # Data layer (API, repositories)
│ │ ├── domain/ # Business logic, entities, use cases
│ │ ├── presentation/ # UI layer (screens, widgets)
│ │ └── app/ # App-specific configurations (providers, routes)
│ │
├── main.dart # Entry point of the app

1. Domain Layer (Business Logic)


The Domain Layer will include the business logic, such as the use cases and
entities (models). This layer does not depend on Flutter or any specific data
source.
Entity Example: DashboardItem
dart
Copy code
// lib/features/dashboard/domain/entities/dashboard_item.dart

class DashboardItem {
final String title;
final String description;
final double value;

DashboardItem({
required this.title,
required this.description,
required this.value,
});
}
Use Case Example: FetchDashboardData
dart
Copy code
// lib/features/dashboard/domain/usecases/fetch_dashboard_data.dart

import 'package:dartz/dartz.dart';
import '../../data/repositories/dashboard_repository.dart';
import '../entities/dashboard_item.dart';
import '../../../core/error/failures.dart';

class FetchDashboardData {
final DashboardRepository repository;

FetchDashboardData(this.repository);
Future<Either<Failure, List<DashboardItem>>> execute() {
return repository.getDashboardData();
}
}

2. Data Layer (Repositories and API Calls)


The Data Layer will handle data fetching, processing, and storing. This layer
includes data sources (APIs, databases) and repositories that provide data to
the domain layer.
Repository Example: DashboardRepository
dart
Copy code
// lib/features/dashboard/data/repositories/dashboard_repository.dart

import 'package:dartz/dartz.dart';
import '../../domain/entities/dashboard_item.dart';
import '../../domain/repositories/dashboard_repository.dart';
import '../../../core/error/failures.dart';
import '../datasources/remote/dashboard_api.dart';

class DashboardRepositoryImpl implements DashboardRepository {


final DashboardApi api;

DashboardRepositoryImpl(this.api);

@override
Future<Either<Failure, List<DashboardItem>>> getDashboardData() async
{
try {
final data = await api.fetchDashboardData();
return Right(data.map((e) => DashboardItem(
title: e['title'],
description: e['description'],
value: e['value'],
)).toList());
} catch (e) {
return Left(ServerFailure());
}
}
}
API Data Source: DashboardApi
dart
Copy code
// lib/features/dashboard/data/datasources/remote/dashboard_api.dart

import 'dart:convert';
import 'package:http/http.dart' as http;

class DashboardApi {
final http.Client client;

DashboardApi(this.client);

Future<List<Map<String, dynamic>>> fetchDashboardData() async {


final response = await
client.get(Uri.parse('https://example.com/dashboard'));

if (response.statusCode == 200) {
return List<Map<String, dynamic>>.from(json.decode(response.body));
} else {
throw Exception('Failed to load dashboard data');
}
}
}

3. Presentation Layer (UI, State Management, and Navigation)


The Presentation Layer contains Flutter widgets, state management, and
routing/navigation. We'll use Provider for state management.
Dashboard UI (Screen) Example
dart
Copy code
// lib/features/dashboard/presentation/screens/dashboard_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../domain/usecases/fetch_dashboard_data.dart';
import '../../domain/entities/dashboard_item.dart';
import '../../presentation/widgets/dashboard_item_card.dart';
import '../../presentation/state/dashboard_state.dart';

class DashboardScreen extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Dashboard')),
body: FutureBuilder<List<DashboardItem>>(
future: Provider.of<FetchDashboardData>(context).execute(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}

if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available'));
}

final dashboardItems = snapshot.data!;


return ListView.builder(
itemCount: dashboardItems.length,
itemBuilder: (context, index) {
return DashboardItemCard(item: dashboardItems[index]);
},
);
},
),
);
}
}
Dashboard Item Card Widget
dart
Copy code
// lib/features/dashboard/presentation/widgets/dashboard_item_card.dart

import 'package:flutter/material.dart';
import '../../domain/entities/dashboard_item.dart';

class DashboardItemCard extends StatelessWidget {


final DashboardItem item;

DashboardItemCard({required this.item});

@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
trailing: Text(item.value.toString()),
),
);
}
}
State Management Example using Provider
We will create a simple state management system using Provider. Here's how
you could manage the loading and error states.
dart
Copy code
// lib/features/dashboard/presentation/state/dashboard_state.dart

import 'package:flutter/material.dart';
import 'package:dartz/dartz.dart';
import '../../domain/entities/dashboard_item.dart';
import '../../../core/error/failures.dart';

class DashboardState extends ChangeNotifier {


List<DashboardItem>? _items;
Failure? _error;
bool _isLoading = false;

List<DashboardItem>? get items => _items;


Failure? get error => _error;
bool get isLoading => _isLoading;

void setLoading(bool loading) {


_isLoading = loading;
notifyListeners();
}

void setItems(List<DashboardItem> items) {


_items = items;
_error = null;
_isLoading = false;
notifyListeners();
}

void setError(Failure error) {


_error = error;
_items = null;
_isLoading = false;
notifyListeners();
}
}
Adding State to UI
In your DashboardScreen, you can manage loading and error states using
Provider and display them accordingly.

4. Navigation
Flutter provides the Navigator widget for managing navigation. You can
handle routing and navigation within the app using named routes or direct
navigation.
dart
Copy code
// lib/main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'features/dashboard/data/datasources/remote/dashboard_api.dart';
import 'features/dashboard/data/repositories/dashboard_repository.dart';
import 'features/dashboard/domain/usecases/fetch_dashboard_data.dart';
import 'features/dashboard/presentation/screens/dashboard_screen.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => DashboardApi(http.Client())),
Provider(create: (_) =>
DashboardRepositoryImpl(DashboardApi(http.Client()))),
Provider(create: (context) =>
FetchDashboardData(Provider.of<DashboardRepository>(context))),
],
child: MaterialApp(
title: 'Flutter Dashboard',
theme: ThemeData(primarySwatch: Colors.blue),
home: DashboardScreen(),
routes: {
'/dashboard': (context) => DashboardScreen(),
},
),
);
}
}

5. Error Handling and Exceptions


To handle errors and exceptions, define a failure class and use it to return
meaningful error messages.
dart
Copy code
// lib/core/error/failures.dart

class Failure {
final String message;
Failure(this.message);
}

class ServerFailure extends Failure {


ServerFailure() : super('Server failure occurred');
}

class NetworkFailure extends Failure {


NetworkFailure() : super('Network error occurred');
}
Summary of Clean Code Principles:
1. Meaningful Class and Method Names: Names should clearly define
their responsibilities.
2. Small, Focused Methods: Break complex functions into smaller,
reusable methods.
3. Exception Handling: Ensure errors are gracefully handled and
propagate clear error messages.
4. Single Responsibility Principle (SRP): Each class or method should
have one responsibility.
5. DRY (Don’t Repeat Yourself): Avoid repeating logic and create
reusable components or methods.
By following these principles, you'll create a maintainable and scalable web
dashboard app in Flutter using Clean Architecture, effective state
management, and

Here is a full code structure for a Flutter Web Dashboard App that follows
Clean Architecture, uses Provider for state management, handles
navigation, collections, methods, attributes, and error handling.
Folder Structure Overview
bash
Copy code
lib/
├── core/ # Core utilities and shared code
│ ├── error/ # Error handling and exceptions
│ ├── network/ # API calls, network-related classes
│ └── utils/ # Shared helpers and utilities

├── features/ # Feature-based structure
│ ├── dashboard/ # Dashboard-related functionality
│ │ ├── data/ # Data layer (API, repositories)
│ │ ├── domain/ # Business logic, entities, use cases
│ │ ├── presentation/ # UI layer (screens, widgets)
│ │ └── app/ # App-specific configurations (providers, routes)
│ │
├── main.dart # Entry point of the app
1. Core Layer
Error Handling (lib/core/error/failures.dart)
dart
Copy code
// lib/core/error/failures.dart

class Failure {
final String message;
Failure(this.message);
}

class ServerFailure extends Failure {


ServerFailure() : super('Server failure occurred');
}

class NetworkFailure extends Failure {


NetworkFailure() : super('Network error occurred');
}
Network Layer (lib/core/network/network_info.dart)
dart
Copy code
// lib/core/network/network_info.dart

import 'dart:io';

abstract class NetworkInfo {


Future<bool> get isConnected;
}

class NetworkInfoImpl implements NetworkInfo {


@override
Future<bool> get isConnected async {
try {
final result = await InternetAddress.lookup('google.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} on SocketException catch (_) {
return false;
}
}
}
2. Dashboard Feature
Entity (lib/features/dashboard/domain/entities/dashboard_item.dart)
dart
Copy code
// lib/features/dashboard/domain/entities/dashboard_item.dart

class DashboardItem {
final String title;
final String description;
final double value;

DashboardItem({
required this.title,
required this.description,
required this.value,
});
}
Repository
(lib/features/dashboard/domain/repositories/dashboard_repository.d
art)
dart
Copy code
// lib/features/dashboard/domain/repositories/dashboard_repository.dart

import 'package:dartz/dartz.dart';
import '../entities/dashboard_item.dart';
import '../../../core/error/failures.dart';

abstract class DashboardRepository {


Future<Either<Failure, List<DashboardItem>>> getDashboardData();
}
Use Case
(lib/features/dashboard/domain/usecases/fetch_dashboard_data.dart
)
dart
Copy code
// lib/features/dashboard/domain/usecases/fetch_dashboard_data.dart

import 'package:dartz/dartz.dart';
import '../../data/repositories/dashboard_repository.dart';
import '../entities/dashboard_item.dart';
import '../../../core/error/failures.dart';

class FetchDashboardData {
final DashboardRepository repository;

FetchDashboardData(this.repository);
Future<Either<Failure, List<DashboardItem>>> execute() {
return repository.getDashboardData();
}
}
3. Data Layer
API Data Source
(lib/features/dashboard/data/datasources/remote/dashboard_api.dart
)
dart
Copy code
// lib/features/dashboard/data/datasources/remote/dashboard_api.dart

import 'dart:convert';
import 'package:http/http.dart' as http;

class DashboardApi {
final http.Client client;

DashboardApi(this.client);

Future<List<Map<String, dynamic>>> fetchDashboardData() async {


final response = await
client.get(Uri.parse('https://example.com/dashboard'));

if (response.statusCode == 200) {
return List<Map<String, dynamic>>.from(json.decode(response.body));
} else {
throw Exception('Failed to load dashboard data');
}
}
}
Repository Implementation
(lib/features/dashboard/data/repositories/dashboard_repository.dart)
dart
Copy code
// lib/features/dashboard/data/repositories/dashboard_repository.dart

import 'package:dartz/dartz.dart';
import '../../domain/entities/dashboard_item.dart';
import '../../domain/repositories/dashboard_repository.dart';
import '../../../core/error/failures.dart';
import '../datasources/remote/dashboard_api.dart';

class DashboardRepositoryImpl implements DashboardRepository {


final DashboardApi api;

DashboardRepositoryImpl(this.api);

@override
Future<Either<Failure, List<DashboardItem>>> getDashboardData() async
{
try {
final data = await api.fetchDashboardData();
return Right(data.map((e) => DashboardItem(
title: e['title'],
description: e['description'],
value: e['value'],
)).toList());
} catch (e) {
return Left(ServerFailure());
}
}
}
4. Presentation Layer
State Management
(lib/features/dashboard/presentation/state/dashboard_state.dart)
dart
Copy code
// lib/features/dashboard/presentation/state/dashboard_state.dart

import 'package:flutter/material.dart';
import 'package:dartz/dartz.dart';
import '../../domain/entities/dashboard_item.dart';
import '../../../core/error/failures.dart';

class DashboardState extends ChangeNotifier {


List<DashboardItem>? _items;
Failure? _error;
bool _isLoading = false;

List<DashboardItem>? get items => _items;


Failure? get error => _error;
bool get isLoading => _isLoading;

void setLoading(bool loading) {


_isLoading = loading;
notifyListeners();
}

void setItems(List<DashboardItem> items) {


_items = items;
_error = null;
_isLoading = false;
notifyListeners();
}

void setError(Failure error) {


_error = error;
_items = null;
_isLoading = false;
notifyListeners();
}
}
Dashboard Screen UI
(lib/features/dashboard/presentation/screens/dashboard_screen.dart
)
dart
Copy code
// lib/features/dashboard/presentation/screens/dashboard_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../domain/usecases/fetch_dashboard_data.dart';
import '../../domain/entities/dashboard_item.dart';
import '../widgets/dashboard_item_card.dart';
import '../state/dashboard_state.dart';

class DashboardScreen extends StatelessWidget {


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Dashboard')),
body: FutureBuilder<List<DashboardItem>>(
future: Provider.of<FetchDashboardData>(context).execute(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}

if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}

if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available'));
}

final dashboardItems = snapshot.data!;


return ListView.builder(
itemCount: dashboardItems.length,
itemBuilder: (context, index) {
return DashboardItemCard(item: dashboardItems[index]);
},
);
},
),
);
}
}
Dashboard Item Card
(lib/features/dashboard/presentation/widgets/dashboard_item_card.d
art)
dart
Copy code
// lib/features/dashboard/presentation/widgets/dashboard_item_card.dart

import 'package:flutter/material.dart';
import '../../domain/entities/dashboard_item.dart';

class DashboardItemCard extends StatelessWidget {


final DashboardItem item;

DashboardItemCard({required this.item});

@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
trailing: Text(item.value.toString()),
),
);
}
}
5. Main Entry Point and Providers (lib/main.dart)
dart
Copy code
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'features/dashboard/data/datasources/remote/dashboard_api.dart';
import 'features/dashboard/data/repositories/dashboard_repository.dart';
import 'features/dashboard/domain/usecases/fetch_dashboard_data.dart';
import 'features/dashboard/presentation/screens/dashboard_screen.dart';
import 'core/network/network_info.dart';
import 'core/error/failures.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => DashboardApi(http.Client())),
Provider(create: (_) =>
DashboardRepositoryImpl(DashboardApi(http.Client()))),
Provider(create: (context) =>
FetchDashboardData(Provider.of<DashboardRepository>(context))),
ChangeNotifierProvider(create: (_) => DashboardState()),
],
child: MaterialApp(
title: 'Flutter Dashboard',
theme: ThemeData(primarySwatch: Colors.blue),
home: DashboardScreen(),
),
);
}
}

Key Features
1. State Management: Using Provider to manage state.
2. Clean Architecture: Separation of concerns into Domain, Data, and
Presentation layers.
3. Error Handling: Proper handling of exceptions and failures.
4. UI Design: Simple UI with lists of items displayed on the dashboard.
5. Navigation: Handled using Flutter's built-in Navigator.

Running the App


To run the app, make sure you have Flutter

You might also like