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

Clean Architecture中如何发送POST请求?Bloc场景传参需求

问题描述

我目前在项目中通过BlocBuilder在构建阶段发起GET请求(无需绑定点击事件),后续自动填充模型数据,现有代码如下:

BlocProvider初始化代码

return BlocProvider<BannerCubit>(
  create: (context) => sl<BannerCubit>()..loadBanner(),
  child: Scaffold(body: BannerCarousels(),),
);

BannerCarousel组件代码

Widget build(BuildContext context) {
  return BlocBuilder<BannerCubit, BannerState>(
    builder: (context, state) {
      List<BannerEntity> banner = [];
      if(state is BannerLoading) {
        return _loadingIndicator();
      } else if (state is BannerLoaded) {
        banner = state.bannerList;
      }
      print('The following data for the banner came to me from the server: $banner');
      return CarouselSlider.builder(
        // 轮播逻辑...
      );
    }
  );
}

数据源层GET请求代码

Future<List<ProductsModel>> getAllProducts() async {
  final response = await client.get(
    Uri.parse(ConfigUrl.home),
    headers: {'Content-Type': 'application/json'}
  );
  if(response.statusCode == 200) {
    final products = json.decode(response.body);
    print('For banner: $products');
    return (products['products'] as List).map((e) => ProductsModel.fromJson(e)).toList();
  } else {
    throw ServerException();
  }
}

现在我的疑问是:如何将TextFormField控制器中的数据传入POST请求体?是否需要在触发请求的按钮组件中直接构建请求?

注:我刚学习Clean Architecture,不清楚如何发送POST请求、填充请求体及解析响应,希望能看到适合新手的示例代码。


解决方案

遵循Clean Architecture和Bloc的分层原则,不要在按钮组件里直接写请求逻辑,应该把请求逻辑放在数据源/仓库层,状态流转交给Cubit处理。下面是完整的新手友好示例:

步骤1:UI层管理输入控制器

在页面组件中定义TextFormField的控制器,用于获取用户输入:

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            TextFormField(
              controller: _emailController,
              decoration: InputDecoration(hintText: '邮箱'),
              keyboardType: TextInputType.emailAddress,
            ),
            TextFormField(
              controller: _passwordController,
              decoration: InputDecoration(hintText: '密码'),
              obscureText: true,
            ),
            ElevatedButton(
              onPressed: () {
                // 点击时触发Cubit的请求方法,传入输入值
                context.read<LoginCubit>().login(
                      email: _emailController.text.trim(),
                      password: _passwordController.text.trim(),
                    );
              },
              child: Text('登录'),
            ),
            // 用BlocBuilder处理请求状态
            BlocBuilder<LoginCubit, LoginState>(
              builder: (context, state) {
                if (state is LoginLoading) {
                  return CircularProgressIndicator();
                } else if (state is LoginSuccess) {
                  return Text('登录成功!用户ID:${state.userId}');
                } else if (state is LoginFailure) {
                  return Text('登录失败:${state.errorMessage}');
                }
                return SizedBox.shrink();
              },
            ),
          ],
        ),
      ),
    );
  }
}

步骤2:创建处理POST请求的Cubit

定义Cubit和对应的状态,处理请求的状态流转:

// 登录状态基类
abstract class LoginState {}

class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {
  final String userId;
  LoginSuccess(this.userId);
}
class LoginFailure extends LoginState {
  final String errorMessage;
  LoginFailure(this.errorMessage);
}

// 登录Cubit
class LoginCubit extends Cubit<LoginState> {
  final AuthRepository authRepository; // 依赖注入仓库

  LoginCubit(this.authRepository) : super(LoginInitial());

  Future<void> login({required String email, required String password}) async {
    emit(LoginLoading());
    try {
      final userId = await authRepository.login(email: email, password: password);
      emit(LoginSuccess(userId));
    } catch (e) {
      emit(LoginFailure(e.toString()));
    }
  }
}

步骤3:在数据源层实现POST请求

在数据源类中添加POST请求方法,构建并填充请求体:

// 数据源层
class AuthRemoteDataSource {
  final Dio client; // 或你使用的http.Client

  AuthRemoteDataSource(this.client);

  Future<String> login(String email, String password) async {
    final response = await client.post(
      Uri.parse(ConfigUrl.login),
      headers: {'Content-Type': 'application/json'},
      // 将输入参数转为JSON作为请求体
      data: json.encode({
        'email': email,
        'password': password,
      }),
    );

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      return data['user_id']; // 解析响应中的目标数据
    } else {
      throw ServerException(); // 自定义异常类
    }
  }
}

// 仓库层(Clean Architecture中间层)
class AuthRepository {
  final AuthRemoteDataSource remoteDataSource;

  AuthRepository(this.remoteDataSource);

  Future<String> login({required String email, required String password}) async {
    return await remoteDataSource.login(email, password);
  }
}

步骤4:注入依赖并初始化Cubit

和你之前的BannerCubit一样,用get_it(sl)注入依赖,在页面外层包裹BlocProvider:

// 依赖注入初始化
sl.registerLazySingleton<AuthRemoteDataSource>(() => AuthRemoteDataSource(sl()));
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(sl()));
sl.registerFactory<LoginCubit>(() => LoginCubit(sl()));

// 页面路由中
return BlocProvider<LoginCubit>(
  create: (context) => sl<LoginCubit>(),
  child: LoginPage(),
);

关键说明

  1. 分层职责清晰:UI层仅负责收集输入、触发事件、展示状态;Cubit处理业务逻辑和状态流转;数据源层负责实际网络请求。
  2. 请求体填充:通过json.encode()将输入参数转为JSON格式,作为POST请求的data参数传入。
  3. 响应解析:请求成功后从响应体中解析目标数据,通过Cubit将状态同步给UI层。
  4. 异常处理:请求失败时抛出自定义异常,Cubit捕获后转为失败状态,UI层展示错误信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 06:30:55