The presentation layer manages UI and state management (Cubit) codes. It includes cubit, pages, and widgets folders. Moreover, a presentation.dart file exports the presentation layer codes. Cubits and widgets within this layer may be shared across multiple pages within a module. However, if a cubit or widget is exclusive to a single page, it may be placed in its dedicated page folder (e.g., pages/login/widgets/login_button.dart, pages/login/cubit/login_cubit.dart). During refactoring, breaking down a widget from a page into its own file is recommended, utilizing part and part of.

auth module presentation :

What inside presentation directory :

packages/auth/lib/src/presentation
├── cubits
├── pages
│   ├── forgot_pin
│   ├── login
│   ├── login_pin
│   ├── pages.dart
│   ├── register
│   ├── select_taste
│   └── testing
├── presentation.dart
└── widgets

What inside each Page directory :

packages/auth/lib/src/presentation/pages/login
├── cubit
│   ├── form
│   │   └── login_form.dart
│   ├── login_cubit.dart
│   ├── login_cubit.freezed.dart
│   └── login_state.dart
├── login_page.dart
└── widgets
    ├── login_background.dart
    └── login_bottom_sheet.dart

Presentation code example

Main Page File :

import 'package:blur/blur.dart';
import 'package:flutter/material.dart';
import 'package:common_dependency/common_dependency.dart';
 
export 'cubit/login_cubit.dart';
 
part 'widgets/login_background.dart';
part 'widgets/login_bottom_sheet.dart';
 
class LoginPage extends StatelessWidget {
  const LoginPage({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => di<LoginCubit>(),
        ),
        BlocProvider(
          create: (context) => di<BiometricCubit>(),
        ),
      ],
      child: const LoginUI(),
    );
  }
}

Refactored Widget File :

part of '../login_page.dart';
 
class LoginBackground extends StatelessWidget {
  const LoginBackground({
    super.key,
  });
 
  @override
  Widget build(BuildContext context) {
    final string = SharedStr.of(context);
    final theme = context.designThemeExtended;
    return Stack(
      children: [
        Blur(
          blur: 1.0,
          blurColor: theme.neutral400,
          colorOpacity: 0.2,
          child: DesignImage.project(
            const DesignAssetsNetwork(
              'https://www.constructionplusasia.com/wp-content/uploads/2020/12/Pakuwon-Mall-Surabaya-Pakuwon-Group.jpg',
            ),
            height: MediaQuery.of(context).size.height,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
        ),
        Positioned(
          top: context.topPadding + 60,
          right: 16,
          left: 16,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              const LanguageSwitcher().bottomMargin(24),
              _buildText(string, theme),
            ],
          ),
        )
      ],
    );
  }
 
  Center _buildText(SharedStr string, PakuwonTheme theme) {
    return Center(
      child: Column(
        children: [
          Text(
            string.auth_welcome_to_pg_card,
            style: MdsTextStyle.xl.overrideColor(theme.white).bold,
            textAlign: TextAlign.center,
          ),
          Text(
            string.auth_enjoy_exclusive_rewards,
            style: MdsTextStyle.sm.overrideColor(theme.white),
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}
 

Cubit file :

import 'package:common_dependency/common_dependency.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'form/login_form.dart';
part 'login_state.dart';
part 'login_cubit.freezed.dart';
 
class LoginCubit extends Cubit<LoginState>
    with SyncEmit<LoginState>, FormxStateManager, FormxBlocMixin<LoginState>
    implements BasePageListener<LoginState> {
  LoginCubit._({required this.formxMeta, required this.loginUseCase})
      : super(LoginState.initial(formxMeta));
  factory LoginCubit(ValidatePhoneUseCase loginUseCase) =>
      LoginCubit._(formxMeta: LoginFormx(), loginUseCase: loginUseCase);
 
  @override
  final FormxMeta formxMeta;
  final ValidatePhoneUseCase loginUseCase;
 
  void submit() async {
    syncEmit(
        (state) => state.copyWith(status: FormzStatus.submissionInProgress));
    final res = await loginUseCase(
        state.formxState.fieldValue[LoginFormx.fieldNames.phone]);
    res.fold(
      (l) {
        if (l is ValidationError) {
          for (var element in l.error) {
            setFormxErrors(
              element.field ?? '',
              [
                (_) => element.message!.message,
              ],
            );
          }
        }
        syncEmit(
          (state) => state.copyWith(
            status: FormzStatus.submissionFailure,
            failure: l,
          ),
        );
      },
      (r) {
        syncEmit(
          (state) => state.copyWith(
            status: FormzStatus.submissionSuccess,
            userLoginType: r,
          ),
        );
      },
    );
  }
 
  @override
  clearErrorState() {
    syncEmit((state) => state.copyWith(failure: null));
  }
}
 

state file :

part of 'login_cubit.dart';
 
@freezed
class LoginState
    with _$LoginState, FormxBlocStateMixin<LoginState>
    implements BasePageState {
  const LoginState._();
  const factory LoginState({
    Failure? failure,
    required FormzStatus status,
    required FormxState formxState,
    UserLoginType? userLoginType,
  }) = _LoginState;
 
  factory LoginState.initial(FormxMeta formxMeta) => LoginState(
        status: FormzStatus.pure,
        formxState: FormxState.withMeta(formxMeta),
      );
 
  get buttonEnabled =>
      formxState.fields[LoginFormx.fieldNames.phone]?.validAndTouched ?? false;
 
  get isLoading => status == FormzStatus.submissionInProgress;
 
  @override
  Failure? get errorState => failure;
 
  @override
  FormzStatus get statusState => status;
 
  @override
  LoginState copyWithForm(FormxState state) => copyWith(formxState: state);
}

For the state management we are using Cubit with single state with the help of freezed package