The use of Flutter_bloc starts with code generation

Framework template code generation

VSCode has a plugin for Flutter Files. After the flutter is installed, right-click to create the corresponding template code in the project bar.

Select [New Small Pack Bloc] and type the page name in lowercase underline style: WESin_test

The corresponding wesIN_test folder is generated

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

Outline the purpose of each page.

  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), ), ); }}Copy the code

The page, as a body, defines the route name. You can use the StatelessWidget. Because normal doesn’t have state management, right

BlocProvider is built in the Builder in State,

body: BlocProvider<RegisterBloc>(
    create: (_) => _registerBloc,
    child: RegisterScreen(widget.phone),
  )
  
Copy the code

A RegisterBloc is registered for the subsequent node, and the widget below this node can be retrieved via Provider. Of

. Of course there are other methods such as context.read

.

Here you can also pass the Bloc to the screen, which makes it easy to get the bloc in the screen. Rather than looking up Bloc in the configuration tree. Slightly reduces the performance cost.

  1. screen

Screen is mainly used to build configuration page content.

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); }); }}Copy the code

BlocListener: Uses BlocListener to listen for status messages. The child does not rebuild the received message. Good for popup loading pages, error messages, etc.

BlocBuilder uses BlocBuilder to wrap the content that needs to be modified. Here, try to use BlocBuilder where updates are involved when assembling the page, and consider the BuildWhen attribute whenever possible. Keep the idea of rebuilding when you have to update.

  1. bloc

Bloc has no logic after encapsulation. His idea is to accept the event of the page and return it to the page as a state change.

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; }}}Copy the code

It is more consistent with the design pattern to put the corresponding event processing inside the event. The code will be clearer without having to make a lot of judgments

    if(event is **event){
        do(**)
    } else if (event is **event) {
        do(**)
    }
Copy the code
  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); }}Copy the code

Abstract events and define event interfaces. The event subclass receives parameters, implements the event interface, and throws a new state to the page.

  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, ); }}Copy the code

State can be considered both abstract state and a single state class. It depends on the specific business. If the property is simple, consider abstraction. There’s no need for abstraction in the case of more attributes.

Thinking summary

  • As the external object of the whole Page, Page is called externally, and bloc needed by this Page is defined and registered.
  • Screen configures the widgets displayed throughout the page. Throw the event to Bloc. Receive the latest state of Bloc and rebuild THE UI.
  • Bloc processes the event and returns it to the page as state

digression

Cubit

There’s also a simple template for cubit, which is actually replacing Bloc with cubit. The event logic is removed.

The event inside Screen calls cubit’s method. Emit (new state) in cubit. Then the page will receive the latest status.

GET

The GET framework is context-independent in terms of usage experience. It should be better for the user experience. GET is also more convenient for simple state management.