EbloX

一个简单的 Flutter 状态管理库。它类似于 Bloc,但它使用大量的注解,并通过 ActionState 的概念将业务逻辑与 UI 分离开。

更简单、更可靠、更易于测试!

示例

一个常见的计数器示例。

添加依赖

dependencies:
  eblox:
  eblox_annotation:

dev_dependencies:
  build_runner:
  eblox_generator:

新的 counter_view_model.dart

import 'package:eblox/eblox.dart';
import 'package:eblox_annotation/eblox_annotation.dart';
import 'package:flutter/cupertino.dart';

part 'counter_view_model.g.dart';

@bloX
class _CounterVModel extends Blox{

  @StateX(name:'CounterState')
  int _counter = 0;

  @ActionX(bind: 'CounterState')
  void _add() async{
    _counter ++;
  }

  @ActionX(bind: 'CounterState')
  void _sub(){
    _counter--;
  }

  @override
  void dispose() {
    super.dispose();
    debugPrint('CounterVModel dispose...');
  }
}

执行 flutter pub run build_runner watch --delete-conflicting-outputs 命令将在当前目录中生成 counter_view_model.g.dart 文件。

它将自动为我们生成 ActionState。接下来,编写 UI 并使用这些 Actions。

import 'package:eblox/blox.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

import 'blox/counter_view_model.dart';

class CounterPage extends StatelessWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child:BloxBuilder<CounterVModel,CounterState>(
            create:()=>CounterVModel(),
            builder: (count) {
              return Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text("$count"),
                    ElevatedButton(
                      onPressed: () {
                        AddAction().to<CounterVModel>();
                      },
                      child: const Text("+"),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        SubAction().to<CounterVModel>();
                      },
                      child: const Text("-"),
                    ),
                  ],
                ),
              );
            }),
      ),
    );
  }
}

如此简单!

用法

派生一个 Blox 类,我们在其中编写业务逻辑。这是 MVVM 中的 ViewModel。请注意,为了方便代码生成注解,类名必须以 _ 前缀开头。最后,我们在该类上使用了 @bloX 注解。

@StateX 用于修饰我们需要的 state,它会自动生成一个指定名称的 State 类来封装修改后的数据。

// **************************************************************************
// BloxStateGenerator
// **************************************************************************

class CounterState<T> extends BloxSingleState<T> {
  CounterState(data) : super(data);
}

如果您不指定名称,状态类将根据默认规则生成。例如

@StateX()
Color _color = Colors.white; 

将生成 ColorState

@ActionX 用于生成 Action 类,也可以指定名称。bind 用于指定要将此 Action 关联到哪个 State 类。此外,它还将修饰的方法与生成的 Action 关联起来,并在发送 Action 时调用此方法。

目前还提供了一个 @AsyncX 注解来修饰异步状态。

part 'search_view_model.g.dart';

@bloX
class _SearchVModel extends Blox{

  @AsyncX(name: 'SongListState')
  SongListModel _songModel = SongListModel();

  @bindAsync
  @ActionX(bind: 'SongListState')
  BloxAsyncTask<SongListModel> _search(String name){
    return (){
      return  SearchService.search(name);
    };
  }
}

@ActionX 修饰的方法也可以声明参数,生成的类将自动包含这些参数。

// **************************************************************************
// BloxActionGenerator
// **************************************************************************

class SearchAction extends BloxAction {
  SearchAction(String name) : super.argsByPosition([name]);
}

要将 Action 方法与异步状态关联,您需要添加另一个注解 @bindAsync。被 @bindAsync 注解的方法必须返回 BloxAsyncTask<T> 类型,其中泛型 T 是我们需要异步加载的数据类型。

在 UI 中,您可以使用 BloxView 来处理异步状态。

class SearchPage extends StatelessWidget {
  SearchPage({Key? key}) : super(key: key);

  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Song search'),),
      body: SafeArea(
        child: Column(
          children: [
            TextField(
              controller: _controller,
              decoration: InputDecoration(
                contentPadding: const EdgeInsets.symmetric(horizontal: 16),
                  suffix: IconButton(
                icon: const Icon(Icons.search_rounded),
                onPressed: (){
                  if(_controller.text.isNotEmpty) {
                    SearchAction(_controller.text).to<SearchVModel>();
                  }
                },
              )),
            ),
            Flexible(
                child: BloxView<SearchVModel, SongListState<SongListModel>>(
              create: () => SearchVModel(),
              onLoading: () => const Center(child: CircularProgressIndicator()),
              onEmpty: ()=> const Center(child: Text("Empty")),
              builder: (state) {
                return ListView.builder(
                    itemCount: state.data.songs.length,
                    itemBuilder: (ctx, i) {
                      return Container(
                        alignment: Alignment.center,
                        height: 40,
                        child: Text(state.data.songs[i],style: const TextStyle(color: Colors.blueGrey,fontSize: 20),),
                      );
                    });
              },
            )),
          ],
        ),
      ),
    );
  }
}

BloxView 提供了 onLoadingonEmptyonErrorbuilder 来处理加载期间和加载后的 UI 显示。

请注意,如果您希望 onEmpty 生效,则自定义数据类型应 mixin BloxData

class SongListModel with BloxData{

  SongListModel({UnmodifiableListView<String>? songs}){
    if(songs !=null) this.songs = songs;
  }

  UnmodifiableListView<String> songs = UnmodifiableListView([]);

  @override
  bool get isEmpty => songs.isEmpty;
}

有关详细示例,请在此 查看。

GitHub

查看 Github