0% found this document useful (0 votes)
9 views1 page

Sodapdf

This document provides a comprehensive guide on implementing Clean Architecture in a Flutter application, specifically focusing on a login feature. It outlines the necessary dependencies, folder structure, and detailed code examples for each layer of the architecture, including entities, use cases, repositories, and presentation layers. The guide also includes instructions for setting up dependency injection and managing authentication state using Bloc.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views1 page

Sodapdf

This document provides a comprehensive guide on implementing Clean Architecture in a Flutter application, specifically focusing on a login feature. It outlines the necessary dependencies, folder structure, and detailed code examples for each layer of the architecture, including entities, use cases, repositories, and presentation layers. The guide also includes instructions for setting up dependency injection and managing authentication state using Bloc.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

Search Write Sign up Sign in

Guia Flutter Clean Architecture


Alejandro Mancilla Umsa · Follow
6 min read · Mar 9, 2024

Para la siguiente guia usaremos la lógica de inicio de sesión en


login_page.dart y mostrar cómo interactúa con toda la estructura de Clean
Architecture, primero necesitaremos definir cada parte de la arquitectura.
Vamos a crear una versión simplificada que cubra los aspectos clave. Esto
incluirá la entidad User , el caso de uso LoginUseCase , el repositorio
AuthRepository , y finalmente, el Bloc o Cubit para manejar el estado de la
página de inicio de sesión.

Desde el main.dart hasta cada capa de la arquitectura, vamos a estructurar


un flujo de autenticación. Este flujo incluirá MultiProvider para inyectar
dependencias, y un Bloc para manejar el estado de la autenticación. Por
simplicidad, este ejemplo simulará la obtención de datos de usuario desde
una fuente de datos ficticia.

1. Dependencias en pubspec.yaml

Primero, asegúrate de tener las siguientes dependencias en tu pubspec.yaml :

dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.4
http: ^1.2.0
provider: ^6.1.2
internet_connection_checker: ^1.0.0+1

2. Estructura de Carpetas
La estructura de carpetas siguiendo Clean Architecture y Atomic Design
para este ejemplo sería:

/lib
/main.dart
/src
/core
/error
exceptions.dart
failures.dart
/network
network_info.dart
/features
/auth
/data
/datasources
auth_remote_data_source.dart
/models
user_model.dart
/repositories
auth_repository_impl.dart
/domain
/entities
user.dart
/repositories
auth_repository.dart
/usecases
login_usecase.dart
/presentation
/bloc
auth_bloc.dart
/pages
login_page.dart
home_page.dart
/injection_container.dart

A continuacion se muestra en orden la linea de creacion de archivos que se


debe seguir.

Ayuda en comando para crear la estructura de carpetas

Mac
mkdir -p lib/src/core/error lib/src/core/network lib/src/core/theme lib/src/core/utils
lib/src/features/example/data/datasources lib/src/features/example/data/models
lib/src/features/example/data/repositories lib/src/features/example/domain/entities
lib/src/features/example/domain/repositories
lib/src/features/example/domain/usecases
lib/src/features/example/presentation/bloc
lib/src/features/example/presentation/pages
lib/src/features/example/presentation/widgets lib/src/ui/atoms lib/src/ui/molecules
lib/src/ui/organisms lib/src/ui/templates

Windows
mkdir lib\src\core\error lib\src\core\network lib\src\core\theme lib\src\core\utils
lib\src\features\example\data\datasources lib\src\features\example\data\models
lib\src\features\example\data\repositories lib\src\features\example\domain\entities
lib\src\features\example\domain\repositories
lib\src\features\example\domain\usecases
lib\src\features\example\presentation\bloc
lib\src\features\example\presentation\pages
lib\src\features\example\presentation\widgets lib\src\ui\atoms lib\src\ui\molecules
lib\src\ui\organisms lib\src\ui\templates

Primero algunos archivos

NetworkInfoImpl
/lib/src/core/network/network_info.dart

import 'package:internet_connection_checker/internet_connection_checker.dart';

abstract class NetworkInfo {


Future<bool> get isConnected;
}

class NetworkInfoImpl implements NetworkInfo {


final InternetConnectionChecker connectionChecker;

NetworkInfoImpl(this.connectionChecker);

@override
Future<bool> get isConnected => connectionChecker.hasConnection;
}

ServerException & CacheException


/lib/src/core/error/exceptions.dart

class ServerException implements Exception {


final String message;

ServerException(this.message);

@override
String toString() => "ServerException: $message";
}

class CacheException implements Exception {


final String message;

CacheException(this.message);

@override
String toString() => "CacheException: $message";
}

ApiClient
/lib/src/core/network/api_client.dart

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


import 'dart:convert';
import 'dart:io';

class ApiClient {
final http.Client httpClient;

ApiClient({required this.httpClient});

Future<dynamic> get(String url) async {


final response = await httpClient.get(Uri.parse(url));

if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw ServerException(response.body);
}
}

// Aquí podrías agregar más métodos para POST, PUT, DELETE, etc.
}

Failure
/lib/src/core/error/failures.dart

abstract class Failure {


final String message;

Failure(this.message);

@override
String toString() => message;
}

class ServerFailure extends Failure {


ServerFailure(String message) : super(message);
}

class CacheFailure extends Failure {


CacheFailure(String message) : super(message);
}

Ahora seguiremos la linea de archivos que se deben crear EN ORDEN para


evitar la falta de depedencias

1. Entidad (Domain Layer)


/auth/domain/entities/user.dart

class User {
final String id;
final String name;
final String email;

User({required this.id, required this.name, required this.email});


}

2. Caso de Uso (Domain Layer)


/auth/domain/usecases/login_usecase.dart

import '../repositories/auth_repository.dart';
import '../entities/user.dart';

class LoginUseCase {
final AuthRepository repository;

LoginUseCase(this.repository);

Future<User> call(String email, String password) async {


return repository.login(email, password);
}
}

3. Repositorio (Domain Layer)


/auth/domain/repositories/auth_repository.dart

import '../entities/user.dart';

abstract class AuthRepository {


Future<User> login(String email, String password);
}

4. Repositorio Implementación (Data Layer)


/auth/data/repositories/auth_repository_impl.dart

import '../../../../core/network/network_info.dart';
import '../datasources/auth_remote_data_source.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart';

class AuthRepositoryImpl implements AuthRepository {


final AuthRemoteDataSource remoteDataSource;
final NetworkInfo networkInfo;

AuthRepositoryImpl({required this.remoteDataSource, required this.networkInfo});

@override
Future<User> login(String email, String password) async {
if (await networkInfo.isConnected) {
try {
final userModel = await remoteDataSource.login(email, password);
return User(id: userModel.id, name: userModel.name, email: userModel.email);
} on ServerException catch (e) {
throw ServerFailure(e.message);
}
} else {
throw ServerFailure("No Internet connection");
}
}
}

5. DataSource Remoto (Data Layer)


/auth/data/datasources/auth_remote_data_source.dart

import '../models/user_model.dart';

abstract class AuthRemoteDataSource {


Future<UserModel> login(String email, String password);
}

class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {


final ApiClient apiClient;

AuthRemoteDataSourceImpl({required this.apiClient});

@override
Future<UserModel> login(String email, String password) async {
final response = await apiClient.get('endpoint_to_login');// cambiar a url usado

if (response is! Map) {


throw ServerException('Invalid response format');
}

return UserModel.fromJson(response);
}
}

6. Modelo (Data Layer)


/auth/data/models/user_model.dart

import '../../domain/entities/user.dart';

class UserModel extends User {


UserModel({required String id, required String name, required String email})
: super(id: id, name: name, email: email);

factory UserModel.fromJson(Map<String, dynamic> json) {


return UserModel(
id: json['id'],
name: json['name'],
email: json['email'],
);
}

Map<String, dynamic> toJson() {


return {
'id': id,
'name': name,
'email': email,
};
}
}

7. Bloc/Cubit (Presentation Layer)


/auth/presentation/bloc/auth_bloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/usecases/login_usecase.dart';
import '../../domain/entities/user.dart';

abstract class AuthEvent {}

class LoginRequested extends AuthEvent {


final String email;
final String password;

LoginRequested(this.email, this.password);
}

abstract class AuthState {}

class AuthInitial extends AuthState {}

class AuthLoading extends AuthState {}

class Authenticated extends AuthState {


final User user;

Authenticated(this.user);
}

class AuthError extends AuthState {


final String message;

AuthError(this.message);
}

class AuthBloc extends Bloc<AuthEvent, AuthState> {


final LoginUseCase loginUseCase;

AuthBloc({required this.loginUseCase}) : super(AuthInitial());

@override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is LoginRequested) {
yield* _mapLoginRequestedToState(event);
}
}

Stream<AuthState> _mapLoginRequestedToState(LoginRequested event) async* {


yield AuthLoading();
try {
final user = await loginUseCase(event.email, event.password);
yield Authenticated(user: user);
} catch (e) {
yield AuthError(message: e.toString());
}
}
}

8. LoginPage (Presentation Layer)


/auth/presentation/pages/login_page.dart

class LoginRequested extends AuthEvent {


final String email;
final String password;

LoginRequested(this.email, this.password);
}

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/auth_bloc.dart';

class LoginPage extends StatefulWidget {


@override
_LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {


final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is Authenticated) {
// Navegar a la pantalla de inicio después de la autenticación
Navigator.of(context).pushReplacementNamed('/home');
} else if (state is AuthError) {
// Mostrar error
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.
}
},
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Usar AuthBloc para iniciar sesión
BlocProvider.of<AuthBloc>(context).add(
LoginRequested(_emailController.text, _passwordController.te
);
}
},
child: Text('Login'),
),
],
),
),
),
),
);
}
}

9. Implementación
Aquí configurarías tu contenedor de inyección de dependencias. Para
mantenerlo simple, puedes usar el paquete get_it :

/lib/injection_container.dart

import 'package:get_it/get_it.dart';
import 'src/features/auth/data/datasources/auth_remote_data_source.dart';
import 'src/features/auth/data/repositories/auth_repository_impl.dart';
import 'src/features/auth/domain/repositories/auth_repository.dart';
import 'src/features/auth/domain/usecases/login_usecase.dart';
import 'src/features/auth/presentation/bloc/auth_bloc.dart';

final sl = GetIt.instance;
void setup() {
// Bloc
sl.registerFactory(() => AuthBloc(loginUseCase: sl()));
// Use cases
sl.registerLazySingleton(() => LoginUseCase(sl()));
// Repository
sl.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl(remoteDataSource
// Data sources
sl.registerLazySingleton<AuthRemoteDataSource>(() => AuthRemoteDataSourceImpl());
}

/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'src/features/auth/presentation/bloc/auth_bloc.dart';
import 'src/features/auth/presentation/pages/login_page.dart';
import 'injection_container.dart' as di;

void main() {
di.setup();
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
BlocProvider(create: (_) => di.sl<AuthBloc>()),
],
child: MaterialApp(
title: 'Flutter Demo',
home: LoginPage(),
),
);
}
}

Hasta aqui finaliza la guia, una seccion adicional para mas casos de uso a
continuacion

/lib/injection_container.dart

import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http;
import 'src/core/network/api_client.dart';
import 'src/core/network/network_info.dart';
import 'src/features/auth/data/datasources/auth_remote_data_source.dart';
import 'src/features/auth/data/repositories/auth_repository_impl.dart';
import 'src/features/auth/domain/repositories/auth_repository.dart';
import 'src/features/auth/domain/usecases/login_usecase.dart';
import 'src/features/auth/domain/usecases/logout_usecase.dart';
import 'src/features/auth/domain/usecases/signup_usecase.dart';
import 'src/features/auth/presentation/bloc/auth_bloc.dart';

final sl = GetIt.instance;

void setup() {
// Bloc
sl.registerFactory(
() => AuthBloc(
loginUseCase: sl(),
logoutUseCase: sl(),
signupUseCase: sl(),
),
);

// Use cases
sl.registerLazySingleton(() => LoginUseCase(sl()));
sl.registerLazySingleton(() => LogoutUseCase(sl()));
sl.registerLazySingleton(() => SignupUseCase(sl()));

// Repository
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(
remoteDataSource: sl(),
networkInfo: sl(),
),
);

// Data sources
sl.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImpl(apiClient: sl()),
);

// Core
sl.registerLazySingleton(() => ApiClient(httpClient: http.Client()));
sl.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl());

// External
sl.registerLazySingleton(() => http.Client());
}

/lib/src/features/auth/domain/usecases/login_usecase.dart

import '../repositories/auth_repository.dart';
import '../entities/user.dart';

class LoginUseCase {
final AuthRepository repository;

LoginUseCase(this.repository);

Future<User> call(String email, String password) async {


return repository.login(email, password);
}
}

class AuthBloc extends Bloc<AuthEvent, AuthState> {


final LoginUseCase loginUseCase;
final LogoutUseCase logoutUseCase;
final SignupUseCase signupUseCase;

AuthBloc({
required this.loginUseCase,
required this.logoutUseCase,
required this.signupUseCase,
}) : super(AuthInitial());

// Manejo de eventos aquí


}

Flutter Clean Architecture Flutter App Development Data Domain Bloc

Written by Alejandro Mancilla Umsa Follow


4 Followers · 1 Following

Desarrollador Flutter

No responses yet

What are your thoughts?

Respond

More from Alejandro Mancilla Umsa

Alejandro Mancilla Umsa Alejandro Mancilla Umsa

CONECTA TU PROYECTO Flutter Firebase Authentication


FLUTTER CON FIREBASE Previamente a implementar Firebase
Los pasos a continuación nos permitirá Authentication debemos conectar el proyect…
conectar cualquier proyecto Flutter con…

Mar 20, 2021 48 Mar 22, 2021 35

Alejandro Mancilla Umsa Alejandro Mancilla Umsa

FIREBASE — ESP8266 INSTALAR SDK ESP8266 —


Actualizado Mayo — 2021 ARDUINO

May 12, 2021 1 May 12, 2021

See all from Alejandro Mancilla Umsa

Recommended from Medium

In Easy Flutter by Yuri Novicow Raj Jani

Flutter. Don’t buy “clean Is Flutter dying in 2025 ? Flutter vs


architecture” Flock
Guide for project managers and indie Hello Flutter developers. I was scrollings the
developers. twitter / x.com and i show a post about the…

Jan 4 184 8 Nov 7, 2024 65 3

Lists

Staff picks Stories to Help You Level-Up


815 stories · 1627 saves at Work
19 stories · 941 saves

Self-Improvement 101 Productivity 101


20 stories · 3310 saves 20 stories · 2787 saves

In Level Up Coding by Mohit Gupta Akshat Arora

Flutter — Why Everyone Is Creating Understanding JWT in Flutter: A


Incorrect Widget Tree. Comprehensive Guide with Code…
Learn how your widget tree works. JSON Web Tokens (JWT) have become a
standard solution for secure communication…

Sep 7, 2024 365 8 Sep 3, 2024 3

Syed Abdul Basit In Mr. Plan ₿ Publication by Sasikumar

Cubit to Cubit Communication Flutter MVVM vs Clean


In this article, we will explore the Architecture: Understanding the…
Free Membership
communication between Cubits. We’ll delve…

Oct 14, 2024 102 1 Distraction-free reading.


Dec 19, 2024 No ads.
2 Read member-only stories

Organize your knowledge with lists and Support writers you read most
highlights.
Earn money for your writing

Sign up to discover human stories that Tell your story. Find your audience.
Listen to audio narrations
deepen your understandingSeeofmorethe world.
recommendations
Read offline with the Medium app

Sign up for free Try for $5/month

You might also like