Monday, January 26, 2026
HomeFlutterHow to implement Login and Signup pages using Bloc in Flutter

How to implement Login and Signup pages using Bloc in Flutter

Let’s go step-by-step through how to implement Login and Signup pages using BLoC in Flutter — following Clean Architecture principles (data → domain → presentation) and explaining each part clearly.


🧱 Project Overview

We’ll build:

  • Splash → Login → Signup → Home
  • Using BLoC state management
  • With DummyJSON API
    • Login API → https://dummyjson.com/auth/login
    • Signup API → https://dummyjson.com/users/add

🗂 Folder Structure (Clean Architecture + BLoC)

lib/
 ├── core/
 │   └── constants/
 │       └── api_constants.dart
 │
 ├── data/
 │   ├── models/
 │   │   ├── user_model.dart
 │   └── repositories/
 │       └── auth_repository.dart
 │
 ├── domain/
 │   └── entities/
 │       └── user_entity.dart
 │
 ├── presentation/
 │   ├── blocs/
 │   │   ├── login/
 │   │   │   ├── login_bloc.dart
 │   │   │   ├── login_event.dart
 │   │   │   └── login_state.dart
 │   │   ├── signup/
 │   │   │   ├── signup_bloc.dart
 │   │   │   ├── signup_event.dart
 │   │   │   └── signup_state.dart
 │   ├── screens/
 │   │   ├── login_screen.dart
 │   │   ├── signup_screen.dart
 │   │   └── home_screen.dart
 │   └── widgets/
 │       └── custom_text_field.dart
 │
 └── main.dart

Step 1️⃣ — Define API Constants

lib/core/constants/api_constants.dart

class ApiConstants {
  static const String baseUrl = "https://dummyjson.com";
  static const String login = "$baseUrl/auth/login";
  static const String signup = "$baseUrl/users/add";
}

Step 2️⃣ — Create User Model & Entity

lib/domain/entities/user_entity.dart

class UserEntity {
  final int id;
  final String username;
  final String email;
  final String token;

  UserEntity({
    required this.id,
    required this.username,
    required this.email,
    required this.token,
  });
}

lib/data/models/user_model.dart

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

class UserModel extends UserEntity {
  UserModel({
    required super.id,
    required super.username,
    required super.email,
    required super.token,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'] ?? 0,
      username: json['username'] ?? '',
      email: json['email'] ?? '',
      token: json['token'] ?? '',
    );
  }
}

Step 3️⃣ — Create Auth Repository

lib/data/repositories/auth_repository.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../core/constants/api_constants.dart';
import '../models/user_model.dart';

class AuthRepository {
  Future<UserModel> login(String username, String password) async {
    final response = await http.post(
      Uri.parse(ApiConstants.login),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'username': username, 'password': password}),
    );

    if (response.statusCode == 200) {
      return UserModel.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to login: ${response.body}');
    }
  }

  Future<UserModel> signup(String username, String email, String password) async {
    final response = await http.post(
      Uri.parse(ApiConstants.signup),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({
        'username': username,
        'email': email,
        'password': password,
      }),
    );

    if (response.statusCode == 200 || response.statusCode == 201) {
      return UserModel.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to signup: ${response.body}');
    }
  }
}

Step 4️⃣ — Create BLoC for Login

login_event.dart

import 'package:equatable/equatable.dart';

abstract class LoginEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class LoginButtonPressed extends LoginEvent {
  final String username;
  final String password;

  LoginButtonPressed(this.username, this.password);
}

login_state.dart

import 'package:equatable/equatable.dart';
import '../../../domain/entities/user_entity.dart';

abstract class LoginState extends Equatable {
  @override
  List<Object?> get props => [];
}

class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {
  final UserEntity user;
  LoginSuccess(this.user);
}
class LoginFailure extends LoginState {
  final String message;
  LoginFailure(this.message);
}

login_bloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../data/repositories/auth_repository.dart';
import '../../../domain/entities/user_entity.dart';
import 'login_event.dart';
import 'login_state.dart';

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final AuthRepository authRepository;

  LoginBloc(this.authRepository) : super(LoginInitial()) {
    on<LoginButtonPressed>((event, emit) async {
      emit(LoginLoading());
      try {
        final user = await authRepository.login(event.username, event.password);
        emit(LoginSuccess(user));
      } catch (e) {
        emit(LoginFailure(e.toString()));
      }
    });
  }
}

Step 5️⃣ — Create BLoC for Signup

signup_event.dart

import 'package:equatable/equatable.dart';

abstract class SignupEvent extends Equatable {
  @override
  List<Object> get props => [];
}

class SignupButtonPressed extends SignupEvent {
  final String username;
  final String email;
  final String password;

  SignupButtonPressed(this.username, this.email, this.password);
}

signup_state.dart

import 'package:equatable/equatable.dart';
import '../../../domain/entities/user_entity.dart';

abstract class SignupState extends Equatable {
  @override
  List<Object?> get props => [];
}

class SignupInitial extends SignupState {}
class SignupLoading extends SignupState {}
class SignupSuccess extends SignupState {
  final UserEntity user;
  SignupSuccess(this.user);
}
class SignupFailure extends SignupState {
  final String message;
  SignupFailure(this.message);
}

signup_bloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../data/repositories/auth_repository.dart';
import '../../../domain/entities/user_entity.dart';
import 'signup_event.dart';
import 'signup_state.dart';

class SignupBloc extends Bloc<SignupEvent, SignupState> {
  final AuthRepository authRepository;

  SignupBloc(this.authRepository) : super(SignupInitial()) {
    on<SignupButtonPressed>((event, emit) async {
      emit(SignupLoading());
      try {
        final user = await authRepository.signup(event.username, event.email, event.password);
        emit(SignupSuccess(user));
      } catch (e) {
        emit(SignupFailure(e.toString()));
      }
    });
  }
}

Step 6️⃣ — Create Login & Signup Screens

login_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/login/login_bloc.dart';
import '../blocs/login/login_event.dart';
import '../blocs/login/login_state.dart';
import 'home_screen.dart';
import 'signup_screen.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _username = TextEditingController();
  final _password = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: BlocConsumer<LoginBloc, LoginState>(
        listener: (context, state) {
          if (state is LoginSuccess) {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(builder: (_) => const HomeScreen()),
            );
          } else if (state is LoginFailure) {
            ScaffoldMessenger.of(context)
                .showSnackBar(SnackBar(content: Text(state.message)));
          }
        },
        builder: (context, state) {
          if (state is LoginLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: [
                TextField(controller: _username, decoration: const InputDecoration(labelText: 'Username')),
                TextField(controller: _password, decoration: const InputDecoration(labelText: 'Password'), obscureText: true),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read<LoginBloc>().add(
                      LoginButtonPressed(_username.text, _password.text),
                    );
                  },
                  child: const Text('Login'),
                ),
                TextButton(
                  onPressed: () {
                    Navigator.push(context, MaterialPageRoute(builder: (_) => const SignupScreen()));
                  },
                  child: const Text("Don't have an account? Sign up"),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

signup_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/signup/signup_bloc.dart';
import '../blocs/signup/signup_event.dart';
import '../blocs/signup/signup_state.dart';
import 'home_screen.dart';

class SignupScreen extends StatefulWidget {
  const SignupScreen({super.key});

  @override
  State<SignupScreen> createState() => _SignupScreenState();
}

class _SignupScreenState extends State<SignupScreen> {
  final _username = TextEditingController();
  final _email = TextEditingController();
  final _password = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Signup')),
      body: BlocConsumer<SignupBloc, SignupState>(
        listener: (context, state) {
          if (state is SignupSuccess) {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(builder: (_) => const HomeScreen()),
            );
          } else if (state is SignupFailure) {
            ScaffoldMessenger.of(context)
                .showSnackBar(SnackBar(content: Text(state.message)));
          }
        },
        builder: (context, state) {
          if (state is SignupLoading) {
            return const Center(child: CircularProgressIndicator());
          }

          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: [
                TextField(controller: _username, decoration: const InputDecoration(labelText: 'Username')),
                TextField(controller: _email, decoration: const InputDecoration(labelText: 'Email')),
                TextField(controller: _password, decoration: const InputDecoration(labelText: 'Password'), obscureText: true),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read<SignupBloc>().add(
                      SignupButtonPressed(_username.text, _email.text, _password.text),
                    );
                  },
                  child: const Text('Signup'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

Step 7️⃣ — Home Screen

home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: const Center(child: Text('Welcome to Home Screen!')),
    );
  }
}

Step 8️⃣ — Initialize BLoC in main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'data/repositories/auth_repository.dart';
import 'presentation/blocs/login/login_bloc.dart';
import 'presentation/blocs/signup/signup_bloc.dart';
import 'presentation/screens/login_screen.dart';

void main() {
  final authRepository = AuthRepository();
  runApp(MyApp(authRepository: authRepository));
}

class MyApp extends StatelessWidget {
  final AuthRepository authRepository;
  const MyApp({super.key, required this.authRepository});

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(create: (_) => LoginBloc(authRepository)),
        BlocProvider(create: (_) => SignupBloc(authRepository)),
      ],
      child: MaterialApp(
        title: 'Flutter BLoC Auth',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const LoginScreen(),
      ),
    );
  }
}

✅ Output Flow

  1. Login Page
    → Enter username (kminchelle) and password (0lelplR)
    → On success → Goes to Home Page
  2. Signup Page
    → Fill fields
    → On success → Goes to Home Page

🚀 Key Concepts Recap

LayerRoleExample
DataHandles API & JSONAuthRepository
DomainDefines business entitiesUserEntity
PresentationUI + BLoCLoginScreen, SignupBloc
State ManagementHandles UI statesBLoC (loading, success, failure)

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments