November 7, 2025
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 posts

What is difference between const and final keyword in flutter

Dheeraj Pal

How to use firebase in flutter

Dheeraj Pal

How to create first demo app using Flutter step by step guide

Dheeraj Pal

1 comment

Dio interceptors in flutter example - Dheeraj Hitech October 29, 2025 at 5:50 pm

[…] How to implement Login and Signup pages using Bloc in Flutter […]

Reply

Leave a Comment