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

https://github.com/apparence-io/alfreed

Apparence.io state management lib
https://github.com/apparence-io/alfreed

dart dart-web flutter flutter-library state-management

Last synced: 8 months ago
JSON representation

Apparence.io state management lib

Awesome Lists containing this project

README

          


build

License: MIT

# Alfreed
[Apparence.io](https://apparence.io) studio flutter management library.
This lib is used for our apps and is open for all.

This force split business logic / view for your pages.

**This is not a state management lib**

You can combine Alfreed with bloc, rxdart or riverpod for reactive update.

See examples for more infos...*(bloc, riverpod example coming next)*

## Install
Add alfreed in your pubspec and import it.
```dart
import 'package:alfreed/alfreed.dart';
```

## Getting Started

### Create a page builder
```dart
class SecondPage extends AlfreedPage {
SecondPage({Object? args}) : super(args: args);

@override
AlfreedPageBuilder build() {
return AlfreedPageBuilder(
key: ValueKey("presenter"),
builder: (ctx, presenter, model) {
return Scaffold(
appBar: AppBar(title: Text(model.title ?? "")),
// ... you page widgets are here
);
},
presenterBuilder: (context) => MyPresenter(),
interfaceBuilder: (context) => ViewInterface(context),
);
}
}
```

Every build is defered to make our navigation responsible for building them. (see routing section).

* **builder**: build your flutter page content (widgets...)
* **presenterBuilder**: build your presenter (business logic class)
* **interfaceBuilder**: build your view interface (business logic calls this class to interact with our application without knowing flutter). Goal is to hide flutter from our business logic. (***Example: showSnackBar(String message) Without any context in parameters***)
* **key**(***optionnal***): used to get a reference to the presenter

> Note: we extends [AlfreedPage] to handle hot reload. Without hot reload we could remove this layer.

### Create a presenter
Create a presenter extending ```Presenter``` class.
This is where you write your business logic.
There is one and only one presenter instance available in the widget tree.

>An instance of this will be available in the builder method seen in the previous stage (Create a page builder).

```dart
class MyPresenter extends Presenter {
// ... write you business logic methods here
}
```

### Create state class
Basically this will contain everything you want to show on your page. This model is a simple class where you can wrap models ```ValueNotifier``` or ```Stream```.

```dart
class ViewInterface extends AlfreedView {
ViewInterface(BuildContext context) : super(context: context);

/// ... all your attributes you want to use in your page

void showMessage(String message) {
final snackBar = SnackBar(content: Text(message));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
```

## Routing
You can push our page widget in your app router.

Like this:

```dart
Route route(RouteSettings settings) {
switch(settings.name) {
case "/":
return MaterialPageRoute(builder: FirstPage());
default:
return MaterialPageRoute(builder: SecondPage());
}
}
```

## Persist page state after disposed page
> **Use case**: you want to navigate to a tab and keep your page state across rebuild. You can use this

Use ```rebuildIfDisposed``` parameter.

*Example:*
```dart
class SecondPage extends AlfreedPage {
@override
AlfreedPageBuilder build() {
return AlfreedPageBuilder(
key: ValueKey("presenter"),
builder: (ctx, presenter, model) => Scaffold(appBar: AppBar(title: Text(model.title ?? ""))),
presenterBuilder: (context) => MyPresenter(),
interfaceBuilder: (context) => ViewInterface(context),
rebuildIfDisposed: false, /// this force the builder to not recreate our presenter. Only content will be rebuilt after recreating page.
);
}
}
```

## Responsive state management
Our build method contains a special context named ```AlfredContext```.
This class contains a ```Device``` attribute you can use to make your view for different device sizes.
> We used twitter bootstrap sizes ref to create range of devices

Device type can be :
* phone (***0px - 576px]***)
* tablet (***[576px - 992px]***)
* large (***[992px - 1200px]***)
* xlarge (***more than 1920px large***)

Use example:
```dart
AlfreedPageBuilder(
key: ValueKey("presenter"),
builder: (ctx, presenter, model) {
return Scaffold(
floatingActionButton: ctx.device < Device.large()
? FloatingActionButton(
backgroundColor: Colors.redAccent,
onPressed: () =>
presenter.addTodoWithRefresh("Button Todo created"),
child: Icon(Icons.plus_one),
)
: null,
);
},
presenterBuilder: (context) => MyPresenter(),
interfaceBuilder: (context) => ViewInterface(context),
);
```
Here our floating button will be available only for devices smaller than large (phone and tablets).


## Animations
To create animations on your page you can use ```AlfreedPageBuilder``` factories:
- ```AlfreedPageBuilder.animated(...)``` for single animation controller
- ```AlfreedPageBuilder.animatedMulti(...)``` for multiple animations controller.
Then you have access to builder methods for your animations.

Basically animations are accessed through a map where you name them. This can help finding your animations back when you need them.

> Animation(s) controller(s) and their animations will be available in your presenter and AlfreedPageBuilder builder methods within context.

#### example:
```dart
AlfreedPageBuilder.animated(
singleAnimControllerBuilder: (ticker) {
var controller =
AnimationController(vsync: ticker, duration: Duration(seconds: 1));
var animation1 = CurvedAnimation(
parent: controller, curve: Interval(0, .4, curve: Curves.easeIn));
var animation2 = CurvedAnimation(
parent: controller, curve: Interval(0, .6, curve: Curves.easeIn));
return {
'': AlfreedAnimation(controller, subAnimations: [animation1, animation2])
};
},
animListener: (context, presenter, model) {
if (model.animate) {
context.animations!.values.first.controller.forward(); //SIMPLIFY
}
},
builder: (ctx, presenter, model) => ...,
presenterBuilder: (context) => MyPresenter(),
interfaceBuilder: (context) => ViewInterface(context),
);
```

Access to animations within presenter and start the first one:
```dart
animations!.values.first.controller.forward();
```

## Page arguments
You can pass arguments from routing directly to your presenter like this
```dart
Route route(RouteSettings settings) {
switch (settings.name) {
case '/second':
return MaterialPageRoute(builder: SecondPage(args: settings.arguments));
default:
return MaterialPageRoute(builder: myPageBuilder.build);
}
}
```
now you can access args directly inside your presenter using:
```dart
this.args
```


## Developper hot reload
Presenter has a ```rebuildOnHotReload``` that will trigger every time the user hot reloads and call onInit again.
By default rebuildOnHotReload is False, so state will be preserved.

### Handle behavior on hot reload
Override ```onReassemble``` in your presenter.

## Test

### Get presenter ref
Use ```AlfreedUtils``` to get a reference of your presenter instance.
```dart
var presenter = AlfreedUtils.getPresenterByKey(
tester, ValueKey("presenter"));
```

* The Key must be unique and added to the ```AlfreedPageBuilder``` seen on step (***Create a page builder***)
* The application must be started using a pumpWidget
* the page is correctly built

### Get data from child widget
You can directly get your viewmodel from an Alfreedpage child like using this :
```dart
final myModel = PresenterInherited.dataOf(context);
```

### Mock presenter
Prefer using a real presenter but in some case this helps.

> ***Doc incoming***

## VsCode snippets
Preferences > User snippets
```json
"Alfreed template": {
"prefix": "alf",
"description": "create an Alfreed templated page",
"body": [
"import 'package:flutter/material.dart';",
"import 'package:alfreed/alfreed.dart';",
"",
"class ${1:name}ViewModel {}",
"",
"class ${1:name}ViewInterface extends AlfreedView {",
" ${1:name}ViewInterface(BuildContext context) : super(context: context);",
"}",
"",
"class ${1:name}Page extends AlfreedPage<${1:name}Presenter, ${1:name}ViewModel, ${1:name}ViewInterface> {",
"",
" ${1:name}Page({Object? args, Key? key}) : super(args: args, key: key);",
"",
" @override",
" AlfreedPageBuilder<${1:name}Presenter, ${1:name}ViewModel, ${1:name}ViewInterface> build() {",
" return AlfreedPageBuilder(",
" key: ValueKey('presenter'),",
" presenterBuilder: (context) => ${1:name}Presenter(),",
" interfaceBuilder: (context) => ${1:name}ViewInterface(context),",
" builder: (context, presenter, model) => null, //TODO",
" );",
" }",
"}",
"",
"class ${1:name}Presenter extends Presenter<${1:name}ViewModel, ${1:name}ViewInterface> {",
"",
" ${1:name}Presenter() : super(state: ${1:name}ViewModel());",
"}"
]
}
```







Developed with 💙  by Apparence.io