{"id":30874195,"url":"https://github.com/apparence-io/alfreed","last_synced_at":"2025-10-14T15:06:25.083Z","repository":{"id":41302101,"uuid":"366306095","full_name":"Apparence-io/alfreed","owner":"Apparence-io","description":"Apparence.io state management lib","archived":false,"fork":false,"pushed_at":"2023-06-29T08:04:12.000Z","size":122,"stargazers_count":5,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-08T00:32:54.615Z","etag":null,"topics":["dart","dart-web","flutter","flutter-library","state-management"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/alfreed","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Apparence-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-05-11T08:14:47.000Z","updated_at":"2025-08-25T16:58:37.000Z","dependencies_parsed_at":"2024-02-09T08:47:13.881Z","dependency_job_id":null,"html_url":"https://github.com/Apparence-io/alfreed","commit_stats":{"total_commits":55,"total_committers":7,"mean_commits":7.857142857142857,"dds":0.1636363636363637,"last_synced_commit":"b84cca6fb1d2e4bebb6af9d6ecd57424143c4a3f"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Apparence-io/alfreed","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Apparence-io%2Falfreed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Apparence-io%2Falfreed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Apparence-io%2Falfreed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Apparence-io%2Falfreed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Apparence-io","download_url":"https://codeload.github.com/Apparence-io/alfreed/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Apparence-io%2Falfreed/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279019292,"owners_count":26086709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","dart-web","flutter","flutter-library","state-management"],"created_at":"2025-09-08T00:21:46.054Z","updated_at":"2025-10-14T15:06:25.076Z","avatar_url":"https://github.com/Apparence-io.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/Apparence-io/alfreed/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/workflow/status/Apparence-io/bart/main\" alt=\"build\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/Apparence-io/alfreed\"\u003e\u003cimg src=\"https://codecov.io/gh/Apparence-io/alfreed/branch/master/graph/badge.svg?token=WYSESJJY0P\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-purple.svg\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Alfreed\n[Apparence.io](https://apparence.io) studio flutter management library.\nThis lib is used for our apps and is open for all. \n\nThis force split business logic / view for your pages. \n\n**This is not a state management lib**\u003cbr/\u003e\nYou can combine Alfreed with bloc, rxdart or riverpod for reactive update.\u003cbr/\u003e \nSee examples for more infos...*(bloc, riverpod example coming next)*\n\n## Install\nAdd alfreed in your pubspec and import it. \n```dart\nimport 'package:alfreed/alfreed.dart';\n```\n\n## Getting Started\n\n### Create a page builder\n```dart\nclass SecondPage extends AlfreedPage\u003cMyPresenter, MyModel, ViewInterface\u003e {\n  SecondPage({Object? args}) : super(args: args);\n\n  @override\n  AlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e build() {\n    return AlfreedPageBuilder(\n      key: ValueKey(\"presenter\"),\n      builder: (ctx, presenter, model) {\n        return Scaffold(\n          appBar: AppBar(title: Text(model.title ?? \"\")),\n          // ... you page widgets are here\n        );\n      },\n      presenterBuilder: (context) =\u003e MyPresenter(),\n      interfaceBuilder: (context) =\u003e ViewInterface(context),\n    );\n  }\n}\n```\n\nEvery build is defered to make our navigation responsible for building them. (see routing section).\n\n* **builder**: build your flutter page content (widgets...)\n* **presenterBuilder**: build your presenter (business logic class)\n* **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***)\n* **key**(***optionnal***): used to get a reference to the presenter\n\n\u003e Note: we extends [AlfreedPage] to handle hot reload. Without hot reload we could remove this layer. \n\n### Create a presenter \nCreate a presenter extending ```Presenter``` class. \nThis is where you write your business logic. \nThere is one and only one presenter instance available in the widget tree. \n\n\u003eAn instance of this will be available in the builder method seen in the previous stage (Create a page builder). \n\n```dart\nclass MyPresenter extends Presenter\u003cMyModel, ViewInterface\u003e {\n    // ... write you business logic methods here\n}\n```\n\n### Create state class\nBasically 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```.\n\n```dart\nclass ViewInterface extends AlfreedView {\n  ViewInterface(BuildContext context) : super(context: context);\n\n  /// ... all your attributes you want to use in your page\n\n  void showMessage(String message) {\n    final snackBar = SnackBar(content: Text(message));\n    ScaffoldMessenger.of(context).showSnackBar(snackBar);\n  }\n}\n```\n\n## Routing\nYou can push our page widget in your app router. \n\u003cbr/\u003eLike this:\n\n```dart\nRoute\u003cdynamic\u003e route(RouteSettings settings) {\n  switch(settings.name) {\n      case \"/\":\n        return MaterialPageRoute(builder: FirstPage());\n      default:  \n        return MaterialPageRoute(builder: SecondPage());\n  }\n}\n```\n\n\n## Persist page state after disposed page\n\u003e **Use case**: you want to navigate to a tab and keep your page state across rebuild. You can use this\n\nUse ```rebuildIfDisposed``` parameter.\n\n*Example:*\n```dart\nclass SecondPage extends AlfreedPage\u003cMyPresenter, MyModel, ViewInterface\u003e {\n  @override\n  AlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e build() {\n    return AlfreedPageBuilder(\n      key: ValueKey(\"presenter\"),\n      builder: (ctx, presenter, model) =\u003e Scaffold(appBar: AppBar(title: Text(model.title ?? \"\"))),\n      presenterBuilder: (context) =\u003e MyPresenter(),\n      interfaceBuilder: (context) =\u003e ViewInterface(context),\n      rebuildIfDisposed: false, /// this force the builder to not recreate our presenter. Only content will be rebuilt after recreating page. \n    );\n  }\n}\n```\n\n## Responsive state management\nOur build method contains a special context named ```AlfredContext```. \nThis class contains a ```Device``` attribute you can use to make your view for different device sizes. \n\u003e We used twitter bootstrap sizes ref to create range of devices\n\nDevice type can be :\n* phone (***0px - 576px]***)\n* tablet (***[576px - 992px]***)\n* large (***[992px - 1200px]***)\n* xlarge (***more than 1920px large***)\n\nUse example: \n```dart\nAlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e(\n  key: ValueKey(\"presenter\"),\n  builder: (ctx, presenter, model) {\n    return Scaffold(\n      floatingActionButton: ctx.device \u003c Device.large()\n          ? FloatingActionButton(\n              backgroundColor: Colors.redAccent,\n              onPressed: () =\u003e\n                  presenter.addTodoWithRefresh(\"Button Todo created\"),\n              child: Icon(Icons.plus_one),\n            )\n          : null,\n    );\n  },\n  presenterBuilder: (context) =\u003e MyPresenter(),\n  interfaceBuilder: (context) =\u003e ViewInterface(context),\n);\n```\nHere our floating button will be available only for devices smaller than large (phone and tablets).\n\n\u003chr/\u003e\n\n## Animations\nTo create animations on your page you can use ```AlfreedPageBuilder``` factories:\n- ```AlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e.animated(...)``` for single animation controller \n- ```AlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e.animatedMulti(...)``` for multiple animations controller.\nThen you have access to builder methods for your animations.\n\nBasically animations are accessed through a map where you name them. This can help finding your animations back when you need them.  \n\n\u003e Animation(s) controller(s) and their animations will be available in your presenter and AlfreedPageBuilder builder methods within context. \n\n#### example:\n```dart\n AlfreedPageBuilder\u003cMyPresenter, MyModel, ViewInterface\u003e.animated(\n  singleAnimControllerBuilder: (ticker) {\n    var controller =\n        AnimationController(vsync: ticker, duration: Duration(seconds: 1));\n    var animation1 = CurvedAnimation(\n        parent: controller, curve: Interval(0, .4, curve: Curves.easeIn));\n    var animation2 = CurvedAnimation(\n        parent: controller, curve: Interval(0, .6, curve: Curves.easeIn));\n    return {\n      '': AlfreedAnimation(controller, subAnimations: [animation1, animation2])\n    };\n  },\n  animListener: (context, presenter, model) {\n    if (model.animate) {\n      context.animations!.values.first.controller.forward(); //SIMPLIFY\n    }\n  },\n  builder: (ctx, presenter, model) =\u003e ...,\n  presenterBuilder: (context) =\u003e MyPresenter(),\n  interfaceBuilder: (context) =\u003e ViewInterface(context),\n);\n```\n\nAccess to animations within presenter and start the first one: \n```dart\nanimations!.values.first.controller.forward();\n```\n\n## Page arguments\nYou can pass arguments from routing directly to your presenter like this\n```dart\nRoute\u003cdynamic\u003e route(RouteSettings settings) {\n  switch (settings.name) {\n    case '/second':\n      return MaterialPageRoute(builder: SecondPage(args: settings.arguments));\n    default:\n      return MaterialPageRoute(builder: myPageBuilder.build);\n  }\n}\n```\nnow you can access args directly inside your presenter using:\n```dart\nthis.args \n```\n\n\u003chr/\u003e\n\n## Developper hot reload\nPresenter has a ```rebuildOnHotReload``` that will trigger every time the user hot reloads and call onInit again. \nBy default rebuildOnHotReload is False, so state will be preserved. \n\n### Handle behavior on hot reload\nOverride ```onReassemble``` in your presenter.\n\n\n## Test\n\n### Get presenter ref\nUse ```AlfreedUtils``` to get a reference of your presenter instance. \n```dart\nvar presenter = AlfreedUtils.getPresenterByKey\u003cMyPresenter, MyModel\u003e(\n    tester, ValueKey(\"presenter\"));\n```\n\n* The Key must be unique and added to the ```AlfreedPageBuilder``` seen on step (***Create a page builder***)\n* The application must be started using a pumpWidget\n* the page is correctly built\n\n### Get data from child widget\nYou can directly get your viewmodel from an Alfreedpage child like using this : \n```dart\nfinal myModel = PresenterInherited.dataOf\u003cMyPresenter, MyModel\u003e(context);\n```\n\n### Mock presenter\nPrefer using a real presenter but in some case this helps. \n\n\u003e ***Doc incoming***\n\n## VsCode snippets \nPreferences \u003e User snippets \n```json\n\"Alfreed template\": {\n\t\t\"prefix\": \"alf\",\n\t\t\"description\": \"create an Alfreed templated page\",\n\t\t\"body\": [\n\t\t\t\"import 'package:flutter/material.dart';\",\n\t\t\t\"import 'package:alfreed/alfreed.dart';\",\n\t\t\t\"\",\n\t\t\t\"class ${1:name}ViewModel {}\",\n\t\t\t\"\",\n\t\t\t\"class ${1:name}ViewInterface extends AlfreedView {\",\n\t\t\t\"  ${1:name}ViewInterface(BuildContext context) : super(context: context);\",\n\t\t\t\"}\",\n\t\t\t\"\",\n\t\t\t\"class ${1:name}Page extends AlfreedPage\u003c${1:name}Presenter, ${1:name}ViewModel, ${1:name}ViewInterface\u003e  {\",\n\t\t\t\"\",\n\t\t\t\"  ${1:name}Page({Object? args, Key? key}) : super(args: args, key: key);\",\n\t\t\t\"\",\n\t\t\t\"  @override\",\n\t\t\t\"  AlfreedPageBuilder\u003c${1:name}Presenter, ${1:name}ViewModel, ${1:name}ViewInterface\u003e build() {\",\n\t\t\t\"    return AlfreedPageBuilder(\",\n\t\t\t\"      key: ValueKey('presenter'),\",\n\t\t\t\"      presenterBuilder: (context) =\u003e ${1:name}Presenter(),\",\n\t\t\t\"      interfaceBuilder: (context) =\u003e ${1:name}ViewInterface(context),\",\n\t\t\t\"      builder: (context, presenter, model) =\u003e null, //TODO\",\n\t\t\t\"    );\",\n\t\t\t\"  }\",\n\t\t\t\"}\",\n\t\t\t\"\",\n\t\t\t\"class ${1:name}Presenter extends Presenter\u003c${1:name}ViewModel, ${1:name}ViewInterface\u003e {\",\n\t\t\t\"\",\n\t\t\t\"  ${1:name}Presenter() : super(state: ${1:name}ViewModel());\",\n\t\t\t\"}\"\n\t\t]\n\t}\n```\n\n\u003chr/\u003e\n\u003cbr\u003e\u003cbr\u003e\n\u003ca href=\"https://en.apparence.io\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/Apparence-io/bart/master/.github/img/apparence_logo.png\"\u003e\u003c/a\u003e\n\u003cp\u003e\u003csmall\u003eDeveloped with 💙 \u0026nbsp;by Apparence.io\u003c/small\u003e\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapparence-io%2Falfreed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapparence-io%2Falfreed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapparence-io%2Falfreed/lists"}