阅读 752

Flutter_bloc框架使用笔记,后续估计都不太会用了

Flutter_bloc使用从代码生成说起

框架模板代码生成

VSCode 有个flutter的插件 Flutter Files, 安装后在项目栏,右击可以新建对应的模板代码。

选择【new small pack bloc】,输入page名称 小写下划线风格:wesin_test

对应的会生成一个wesin_test文件夹, 文件夹里有

  • wesin_test_page.dart
  • wesin_test_screen.dart
  • wesin_test_bloc.dart
  • wesin_test_event.dart
  • wesin_test_state.dart

大致说明下每个页面的用途。

  1. page
class RegisterPage extends StatefulWidget {
  static const String routeName = '/register';
  RegisterPage(this.phone, this.verifyCode);
  final String verifyCode;
  final String phone;

  @override
  _RegisterPageState createState() => _RegisterPageState();
}

class _RegisterPageState extends State<RegisterPage> {
  final _registerBloc = RegisterBloc(RegisterState());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(LS.of(context, "register")),
      ),
      body: BlocProvider<RegisterBloc>(
        create: (_) => _registerBloc,
        child: RegisterScreen(widget.phone),
      ),
    );
  }
}

复制代码

作为一个主体的页面,定义了route名称。 可以使用StatelessWidget。因为正常也没什么状态管理

在state里面的builder里构建了BlocProvider

body: BlocProvider<RegisterBloc>(
    create: (_) => _registerBloc,
    child: RegisterScreen(widget.phone),
  )
  
复制代码

这里是为后续的节点注册了一个RegisterBloc, 从这个节点下级的widget就可以通过Provider.of<RegisterBloc>来获取。 当然也可以有其他的方式比如context.read<RegisterBloc>来获取。

这里也可以把bloc传给screen,可以方便在screen里面获取bloc。而不是通过在配置树里查找bloc。能稍微减少些性能消耗。

  1. screen

screen主要是构建配置页面内容用。

class RegisterScreen extends StatelessWidget {
  const RegisterScreen(
    this.phone, {
    Key? key,
  }) : super(key: key);

  final String phone;

  @override
  Widget build(BuildContext context) {
    return BlocListener<RegisterBloc, RegisterState>(
        listener: (ctx, state) {
          if (state.status.isSubmissionInProgress) {
            EasyLoading.show(
                maskType: EasyLoadingMaskType.clear, dismissOnTap: false);
            return;
          }
          if (state.status.isSubmissionFailure) {
            EasyLoading.dismiss();
            var message = LS.of(context, "register_error");
            EasyLoading.showError('$message:${state.errMessage ?? ''}');
            return;
          }
          if (state.status.isSubmissionSuccess) {
            EasyLoading.dismiss();
            showCupertinoDialog(
                context: context,
                builder: (context) {
                  return
                    AlertDialog(
                    content: Text(LS.of(context, "register_success")),
                    actions: [TextButton(onPressed: (){
                      Navigator.of(context).popUntil(ModalRoute.withName("/"));
                    }, child: Text(LS.of(context, "confirm")))],
                  );
                });
          }
        },
        child: Align(
          alignment: Alignment(0, -1),
          child: Padding(
            padding: const EdgeInsets.only(top: 31, left: 36, right:36),
            child: Column(children: [
              _UsernameInput(),
              _PasswordInput(),
              _ConfirmPasswordInput(),
              const SizedBox(
                height: 20,
              ),
              _RegisterButton()
            ]),
          ),
        ));
  }
}

class _RegisterButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<RegisterBloc, RegisterState>(builder: (context, state) {
      return CircleRectButton(
          child: Text(LS.of(context, "register"), style: TextStyle(letterSpacing: 20)),
          onPressed: state.status.isValidated
              ? () async {
                  context.read<RegisterBloc>().add(RegisterSubmitEvent());
                }
              : null);
    });
  }
}
复制代码

BlocListener: 通过BlocListener来监听状态消息,收到消息child不会rebuild。适合拿来弹出加载页,错误信息等。

BlocBuilder 通过BlocBuilder来包装需要修改的内容。 这里的在组装页面的时候尽量在涉及到更新的地方使用 BlocBuilder,也尽可能的考虑buildwhen 属性。保持当必须要更新时才重新build的思路。

  1. bloc

bloc 在封装后没什么逻辑,他的思路就是接受页面的事件,转为状态变更返回给页面。

class RegisterBloc extends Bloc<RegisterEvent, RegisterState> {

  RegisterBloc(RegisterState initialState) : super(initialState);

  @override
  Stream<RegisterState> mapEventToState(
    RegisterEvent event,
  ) async* {
    try {
      yield* event.applyAsync(currentState: state, bloc: this);
    } catch (_, stackTrace) {
      developer.log('$_', name: 'RegisterBloc', error: _, stackTrace: stackTrace);
      yield state;
    }
  }
}

复制代码

将对应的事件处理放在event里面去执行显然更符合设计模式。代码也会更清晰,而不用做很多判断

    if(event is **event){
        do(**)
    } else if (event is **event) {
        do(**)
    }
复制代码
  1. event
@immutable
abstract class RegisterEvent {
  const RegisterEvent();
   Stream<RegisterState> applyAsync({required RegisterState currentState, required RegisterBloc bloc});
}

class UsernameChangeEvent extends RegisterEvent {
  const UsernameChangeEvent(this.username);
  final String username;

  Stream<RegisterState> applyAsync({required RegisterState currentState, required RegisterBloc bloc}) async* {
    var input = UsernameInput.dirty(username);
    var status = Formz.validate([input, currentState.password, currentState.confirmPassword]);
    yield currentState.copyWith(username: input, status: status);
  }

}
复制代码

抽象event,定义事件接口。 子类event来接收参数,实现事件接口并抛出新的状态给页面。

  1. state
class RegisterState extends Equatable {
  final UsernameInput username;
  final PasswordTwoInput password;
  final PasswordTwoInput confirmPassword;
  final FormzStatus status;
  final String? errMessage;
  RegisterState({
    this.username = const UsernameInput.pure(),
    this.password = const PasswordTwoInput.pure(),
    this.confirmPassword = const PasswordTwoInput.pure(),
    this.status = FormzStatus.pure,
    this.errMessage
  });

  @override
  List<Object> get props => [username, password, confirmPassword, status];

  RegisterState copyWith({
    UsernameInput? username,
    PasswordTwoInput? password,
    PasswordTwoInput? confirmPassword,
    FormzStatus? status,
  }) {
    return RegisterState(
      username: username ?? this.username,
      password: password ?? this.password,
      confirmPassword: confirmPassword ?? this.confirmPassword,
      status: status ?? this.status,
    );
  }
}
复制代码

state可以考虑抽象state和单独一个state类。看具体业务吧。如果属性很简单,可以考虑抽象。属性比较多的情况下就没必要抽象了。

思路总结

  • Page作为整个页面的对外对象,给外部调用,同时定义和注册了本页面需要用的bloc.
  • Screen配置了整个页面展示的widget。并把事件抛给bloc.通过接收bloc的最新状态,并rebuild UI.
  • bloc里面处理事件并转为state返回给页面

题外话

Cubit

还有一套cubit的简单模板,其实就是把bloc换成了cubit。去掉了event的逻辑。

screen里面事件调用cubit的方法。在cubit里面emit(new state).如此页面就可以接收到最新的状态了。

GET

在使用体验上GET框架不依赖context。对于使用体验来说应该更好些。对于简单的状态管理,也是GET更方便。

文章分类
Android
文章标签