Flutter 状态管理之Bloc下

本文介绍了如何通过Bloc架构实现业务逻辑与UI的解耦,涉及LessonEvent、网络请求事件处理、数据结构(LessonBean和LessonDetail)及LessonWrap包装,展示了LessonBloc的实现和页面展示的逻辑。
摘要由CSDN通过智能技术生成

这篇是使用 Bloc 来实现业务逻辑与UI分离。主要就是慕课网课程列表的网络请求并且展示。

首先定义一个基础事件的类,如下:

abstract class LessonEvent {}

然后我定义了3种事件:

  1. 发起网络请求事件:
class FetchDataEvent extends LessonEvent {}
  1. 网络请求成功事件:
class FetchDataSuccessEvent extends LessonEvent {}
  1. 网络请求失败的事件:
class FetchDataFailedEvent extends LessonEvent {}

然后定义一个枚举类型,来表示这3种事件,这样就可以在 BlockBuild 中根据枚举类型来展示不同的 UI 了。

enum LoadStatus { loading, success, failed }

课程的数据结构如下:

import 'package:json_annotation/json_annotation.dart';

part 'LessonBean.g.dart';

@JsonSerializable()
class LessonBean {
  int status;
  String msg;
  List<LessonDetail> data;

  LessonBean({required this.status, required this.msg, required this.data});

  factory LessonBean.fromJson(Map<String, dynamic> srcJson) =>
      _$LessonBeanFromJson(srcJson);

  Map<String, dynamic> toJson() => _$LessonBeanToJson(this);

  @override
  bool operator ==(Object other) {
    if (identical(other, this)) return true;

    if (other is LessonBean) {
      return other.runtimeType == this.runtimeType &&
          other.status == this.status &&
          other.msg == this.msg &&
          other.data == this.data;
    } else {
      return false;
    }
  }

  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + status.hashCode;
    result = 37 * result + msg.hashCode;
    result = 37 * result + data.hashCode;
    return result;
  }
}

@JsonSerializable()
class LessonDetail {
  final int id;
  final String name;
  final String picSmall;
  final String picBig;
  final String description;

  LessonDetail(
      {required this.id,
      required this.name,
      required this.picSmall,
      required this.picBig,
      required this.description});

  factory LessonDetail.fromJson(Map<String, dynamic> srcJson) =>
      _$LessonDetailFromJson(srcJson);

  Map<String, dynamic> toJson() => _$LessonDetailToJson(this);

  @override
  bool operator ==(Object other) {
    if (identical(other, this)) {
      return true;
    } else if (other is LessonDetail) {
      return other.runtimeType == this.runtimeType &&
          other.id == this.id &&
          other.name == this.name &&
          other.picSmall == this.picSmall &&
          other.picBig == this.picBig &&
          other.description == this.description;
    } else {
      return false;
    }
  }

  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + id.hashCode;
    result = 37 * result + name.hashCode;
    result = 37 * result + picSmall.hashCode;
    result = 37 * result + picBig.hashCode;
    result = 37 * result + description.hashCode;
    return result;
  }
}

重写了其中的 hashcode 和重载了运算符 ==

然后再定义一个课程的包装类,包含了 LessonBean 和 枚举类型 LoadStatus,如下:

class LessonWrap {
  LessonBean? lessonBean;
  LoadStatus status;

  LessonWrap({required this.lessonBean, required this.status});
}

这个包装类型 LessonWrap 将作为自定义 Bloc 的输出,然后 BlockBuilder中就会观察到数据的改变。

LessonBloc 的代码如下:

class LessonBloc extends Bloc<LessonEvent, LessonWrap> {
  LessonBean? _lessonBean;

  LessonBloc(LessonWrap initial) : super(initial) {
    on<FetchDataEvent>((event, emit) {
      _requestData();
    });

    on<FetchDataSuccessEvent>((event, emit) {
      emit(LessonWrap(lessonBean: _lessonBean, status: LoadStatus.success));
    });

    on<FetchDataFailedEvent>((event, emit) {
      emit(LessonWrap(lessonBean: null, status: LoadStatus.failed));
    });

    /// 初始化 bloc 的时候就去请求数据
    add(FetchDataEvent());
  }

  void _requestData() async {
    _lessonBean = await MukeService().getPersonalLesson(7);

    /// 接口请求太快了,看不到loading圈,所以加个延时
    Timer(Duration(seconds: 2), () {
      if (_lessonBean == null) {
        add(FetchDataFailedEvent());
      } else {
        add(FetchDataSuccessEvent());
      }
    });
  }
}

页面展示的逻辑如下:

class BlocInstanceWidget extends StatelessWidget {
  Widget getRow(LessonDetail detail) {
    return GestureDetector(
      child: Container(
        color: Colors.blueGrey,
        width: double.infinity,
        padding: EdgeInsets.only(bottom: 10, left: 10, top: 10),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("Row >>>  ${detail.name}"),
            SizedBox(
              height: 5,
            ),
            Container(
              child: ClipRRect(
                // 圆角图片
                borderRadius: BorderRadius.circular(8),
                child: Image(
                  image: NetworkImage(detail.picSmall),
                  width: 60,
                  height: 60,
                  fit: BoxFit.fitHeight,
                ),
              ),
            ),
            SizedBox(
              height: 6,
            ),
            Text(
              detail.description,
              style: const TextStyle(
                  color: Colors.brown,
                  fontWeight: FontWeight.bold,
                  fontStyle: FontStyle.italic),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    LessonBloc lessonBloc = BlocProvider.of<LessonBloc>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc Instance'),
      ),
      body: Container(
        child: Column(
          children: [
            BlocBuilder<LessonBloc, LessonWrap>(
              builder: (context, wrap) {
                if (wrap.status == LoadStatus.loading) {
                  return Container(
                      margin: EdgeInsets.only(top: 300),
                      // color: Color(0xffFFFFFF),
                      alignment: Alignment.center,
                      child: CircularProgressIndicator(
                        color: const Color(0xff2A9DFF).withOpacity(0.5),
                      ));
                } else if (wrap.status == LoadStatus.success) {
                  /// 因为 在 Column 中嵌套 ListView ,所以需要加上 Expanded ,
                  /// 否则报 Vertical viewport was given unbounded height
                  return Expanded(
                      child: ListView.separated(
                    shrinkWrap: true,
                    itemCount: wrap.lessonBean!.data.length,
                    itemBuilder: (BuildContext context, int position) {
                      return getRow(wrap.lessonBean!.data[position]);
                    },
                    separatorBuilder: (context, index) {
                      return Divider(
                          height: 1.0, indent: 10, color: Colors.black);
                    },
                  ));
                } else {
                  return Text("请求数据出错!!!!");
                }
              },
              buildWhen: (previous, next) {
                /// 过滤条件,只有2次数据不一致时才刷新
                return previous.lessonBean != next.lessonBean;
              },
            ),
            ElevatedButton(
                onPressed: () {
                  /// 由于我加了过滤条件,即上面的 buildWhen,
                  /// 因为2次刷新的数据是一样的(我重写了 LessonBean 的 hashcode 和 '=='),所以不会刷新界面
                  lessonBloc.add(FetchDataEvent());
                },
                child: Text('刷新数据')),
          ],
        ),
      ),
    );
  }
}

入口代码如下:

void main() {
  runApp(MaterialApp(
      home: BlocProvider<LessonBloc>(
          create: (context) => LessonBloc(
              LessonWrap(lessonBean: null, status: LoadStatus.loading)),
          child: BlocInstanceWidget())));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值