You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Clean Architecture+Bloc的Flutter登录实现中正确传递参数?

问题描述

我正在学习基于Clean Architecture的Flutter开发,登录功能需要向API传递company、username、pin三个参数,当前使用Flutter Bloc和Dio框架。目前代码通过const.dart文件中定义的常量参数可正常运行,但我希望在点击登录按钮时,将三个TextFormField的输入值作为动态参数传递给LoginRepositoryImpl中的API请求,同时遵循Clean Architecture原则。

当前相关代码

登录按钮

_buildLoginButton(context) {
  return OutlinedButton(
    onPressed: () {
      // Dispatch the GetLoginDetails event with the login details
      if (formKey.currentState!.validate()) {
        BlocProvider.of<RemoteLoginDetailsBloc>(context).add(
          GetLoginDetails(
            companyController.text.trim(),
            usernameController.text.trim(),
            pinController.text.trim(),
          ),
        );
      }
    },
    child: const Text("Login"),
  );
}

RemoteLoginBloc

class RemoteLoginDetailsBloc
    extends Bloc<RemoteLoginDetailsEvent, RemoteLoginState> {
  final GetLoginDetailsUseCase _getLoginUseCase;

  RemoteLoginDetailsBloc(this._getLoginUseCase)
      : super(const RemoteLoginDetailsLoading()) {
    on<GetLoginDetails>(onGetLoginDetails);
  }

  void onGetLoginDetails(
      GetLoginDetails event, Emitter<RemoteLoginState> emit) async {
    final dataState = await _getLoginUseCase();

    if (dataState is DataSuccess && dataState.data!.isNotEmpty) {
      emit(RemoteLoginDone(dataState.data!));
    }

    if (dataState is DataFailed) {
      debugPrint(
          'Data Failed Error: ${dataState.error!.message} $loginAPIBaseURL');
      emit(RemoteLoginError(dataState.error!));
    }
  }
}

RemoteLoginState

abstract class RemoteLoginState extends Equatable {
  final List<LoginEntity>? loginDetails;
  final DioException? error;

  const RemoteLoginState({this.loginDetails, this.error});

  @override
  List<Object?> get props => [loginDetails!, error!];
}

class RemoteLoginDetailsLoading extends RemoteLoginState {
  const RemoteLoginDetailsLoading();
}

class RemoteLoginDone extends RemoteLoginState {
  const RemoteLoginDone(List<LoginEntity> login) : super(loginDetails: login);
}

class RemoteLoginError extends RemoteLoginState {
  const RemoteLoginError(DioException error) : super(error: error);
}

RemoteLoginEvent

abstract class RemoteLoginDetailsEvent {
  const RemoteLoginDetailsEvent();
}

class GetLoginDetails extends RemoteLoginDetailsEvent {
  GetLoginDetails();
}

GetLoginDetailsUseCase

class GetLoginDetailsUseCase
    implements UseCase<DataState<List<LoginEntity>>, void> {
  final LoginRepository _loginRepository;

  GetLoginDetailsUseCase(this._loginRepository);

  @override
  Future<DataState<List<LoginEntity>>> call({void params}) {
    return _loginRepository.getLogonDetails();
  }
}

LoginRepository

abstract class LoginRepository {
  // API Methods
  Future<DataState<List<LoginEntity>>> getLogonDetails();
}

LoginRepositoryImpl

class LoginRepositoryImpl implements LoginRepository {
  //final LoginDatabase _loginDatabase;
  final LoginApiService _loginApiService;
  LoginRepositoryImpl(this._loginApiService);

  @override
  Future<DataState<List<LoginModel>>> getLogonDetails() async {
    try {
      final httpResponse = await _loginApiService.getLogonDetails(
        apiKey: telMeApiKey,
        lcomp: lcomp,
        luser: luser,
        lpin: lpin,
      );

      // Log the request and response for debugging
      debugPrint('Request: ${httpResponse.response.requestOptions.uri}');
      debugPrint('Response status code: ${httpResponse.response.statusCode}');

      if (httpResponse.response.statusCode == HttpStatus.ok) {
        return DataSuccess(httpResponse.data);
      } else {
        return DataFailed(DioException(
          error: httpResponse.response.statusMessage,
          response: httpResponse.response,
          type: DioExceptionType.badResponse,
          requestOptions: httpResponse.response.requestOptions,
        ));
      }
    } on DioException catch (e) {
      debugPrint('TelMeLogonRequest - Error:$e');
      return DataFailed(e);
    }
  }
}

修改步骤(遵循Clean Architecture)

1. 更新RemoteLoginEvent,让GetLoginDetails携带登录参数

给事件添加参数并继承Equatable,确保Bloc能正确识别事件差异:

abstract class RemoteLoginDetailsEvent extends Equatable {
  const RemoteLoginDetailsEvent();

  @override
  List<Object?> get props => [];
}

class GetLoginDetails extends RemoteLoginDetailsEvent {
  final String company;
  final String username;
  final String pin;

  const GetLoginDetails(this.company, this.username, this.pin);

  @override
  List<Object?> get props => [company, username, pin];
}

2. 定义参数实体并修改GetLoginDetailsUseCase

在Domain层定义参数封装类,同时更新UseCase的参数类型:

// 登录参数实体(放在domain/entities目录下)
class LoginParams {
  final String company;
  final String username;
  final String pin;

  const LoginParams({
    required this.company,
    required this.username,
    required this.pin,
  });
}

// 修改UseCase
class GetLoginDetailsUseCase
    implements UseCase<DataState<List<LoginEntity>>, LoginParams> {
  final LoginRepository _loginRepository;

  GetLoginDetailsUseCase(this._loginRepository);

  @override
  Future<DataState<List<LoginEntity>>> call({required LoginParams params}) {
    return _loginRepository.getLogonDetails(params);
  }
}

3. 更新LoginRepository抽象方法

让抽象层支持接收登录参数:

abstract class LoginRepository {
  // API Methods
  Future<DataState<List<LoginEntity>>> getLogonDetails(LoginParams params);
}

4. 修改LoginRepositoryImpl,传递动态参数到API

从参数实体中取值替换原有的常量:

class LoginRepositoryImpl implements LoginRepository {
  final LoginApiService _loginApiService;
  LoginRepositoryImpl(this._loginApiService);

  @override
  Future<DataState<List<LoginModel>>> getLogonDetails(LoginParams params) async {
    try {
      final httpResponse = await _loginApiService.getLogonDetails(
        apiKey: telMeApiKey,
        lcomp: params.company,
        luser: params.username,
        lpin: params.pin,
      );

      debugPrint('Request: ${httpResponse.response.requestOptions.uri}');
      debugPrint('Response status code: ${httpResponse.response.statusCode}');

      if (httpResponse.response.statusCode == HttpStatus.ok) {
        return DataSuccess(httpResponse.data);
      } else {
        return DataFailed(DioException(
          error: httpResponse.response.statusMessage,
          response: httpResponse.response,
          type: DioExceptionType.badResponse,
          requestOptions: httpResponse.response.requestOptions,
        ));
      }
    } on DioException catch (e) {
      debugPrint('TelMeLogonRequest - Error:$e');
      return DataFailed(e);
    }
  }
}

5. 更新RemoteLoginDetailsBloc,传递参数给UseCase

从事件中提取参数,封装后传给UseCase:

void onGetLoginDetails(
    GetLoginDetails event, Emitter<RemoteLoginState> emit) async {
  final dataState = await _getLoginUseCase(
    params: LoginParams(
      company: event.company,
      username: event.username,
      pin: event.pin,
    ),
  );

  if (dataState is DataSuccess && dataState.data!.isNotEmpty) {
    emit(RemoteLoginDone(dataState.data!));
  }

  if (dataState is DataFailed) {
    debugPrint(
        'Data Failed Error: ${dataState.error!.message} $loginAPIBaseURL');
    emit(RemoteLoginError(dataState.error!));
  }
}

说明

以上修改严格遵循Clean Architecture分层规则:

  • Presentation层:负责收集用户输入,触发Bloc事件
  • Domain层:定义参数实体和业务规则,依赖抽象而非具体实现
  • Data层:接收Domain层参数,执行具体API请求

内容的提问来源于stack exchange,提问作者RJASSI98

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:20:53