Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/arcticfox1919/eblox

An easy Flutter state management library
https://github.com/arcticfox1919/eblox

dart flutter flutter-package

Last synced: 13 days ago
JSON representation

An easy Flutter state management library

Awesome Lists containing this project

README

        

# EbloX

An easy Flutter state management library.It is similar to Bloc, but it uses a lot of annotations and separates business logic from UI through the concepts of **Action** and **State**.

![](https://picturehost.oss-cn-shenzhen.aliyuncs.com/img/2021-12-02-001.png)

**Simpler, more reliable, easier to test !**

## Example

An example of a common counter.

Add dependency:

```yaml
dependencies:
eblox:
eblox_annotation:

dev_dependencies:
build_runner:
eblox_generator:
```

New `counter_view_model.dart`

```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 with 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...');
}
}
```

Execute `flutter pub run build_runner watch --delete-conflicting-outputs` command will generate the `counter_view_model.g.dart` file in the current directory.

It will automatically generate **Action** and **State** for us. Next, write the UI and use these Actions.

```dart
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(
inject:(injection)=> injection.inject(CounterVModel()),
builder: (count) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("$count"),
ElevatedButton(
onPressed: () {
$$(AddAction());
},
child: const Text("+"),
),
ElevatedButton(
onPressed: () {
$$(SubAction());
},
child: const Text("-"),
),
],
),
);
}),
),
);
}
}
```

**So easy!**

## Usage

Create a class and mix in Blox where we can write our business logic. It is similar to ViewModel in MVVM. But note that we have to use the `@blox` annotation on this class. There is also a case, if we want to be able to access the state generated by the `@StateX` annotation externally, we should also mix in `_$className`, such as `class CounterVModel with Blox,_$CounterVModel`,and then use ` $().counter` accesses the state. refer to [example](https://github.com/arcticfox1919/eblox/tree/main/example/lib).

`@StateX` is used to decorate the state we need, it will automatically generate a State class with a specified name for packaging the modified data:

```dart
// **************************************************************************
// BloxStateGenerator
// **************************************************************************

class CounterState extends BloxSingleState {
CounterState(data) : super(data);
}
```

If you do not specify a name, the state class will be generated according to the default rules. E.g:

```dart
@StateX()
Color _color = Colors.white;
```

Will generate `ColorState`.

`@ActionX` is used to generate the Action class, and the name can also be specified. The `bind` is used to specify which State class to associate this Action with. In addition, it also associates the decorated method with the generated Action, and this method is called when the Action is sent.

An `@AsyncX` annotation is also currently provided to decorate asynchronous state:

```dart
part 'search_view_model.g.dart';

@blox
class SearchVModel with Blox{

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

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

`@ActionX` decorated method can also declare parameters, the generated class will automatically include:

```dart
// **************************************************************************
// BloxActionGenerator
// **************************************************************************

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

To associate an **Action** method with an asynchronous state, you need to add another annotation `@bindAsync`. The method annotated by `@bindAsync` must return the type `BloxAsyncTask`, and the generic `T` is the data we need to load asynchronously type.

In the UI, you can use `BloxView` to handle asynchronous states:

```dart
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));
}
},
)),
),
Flexible(
child: BloxView>(
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` provides `onLoading`, `onEmpty`, `onError`,`builder` to handle the UI display during and after loading.

![](https://picturehost.oss-cn-shenzhen.aliyuncs.com/img/GIF2021-12-3_1-36-46.gif)

Note that if you want `onEmpty` to be valid, then your custom data type should mixin `BloxData`:

```dart
class SongListModel with BloxData{

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

UnmodifiableListView songs = UnmodifiableListView([]);

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

Please check [here](https://github.com/arcticfox1919/eblox/tree/main/example/lib) for detailed examples.