octopus
상태와 중첩 탐색에 초점을 맞춘 플러터용 크로스플랫폼 선언형 라우터입니다. PlugFox가 ❤️로 제작했습니다.
상태와 중첩 탐색에 초점을 맞춘 플러터용 크로스플랫폼 선언형 라우터입니다. PlugFox가 ❤️로 제작했습니다.
{"sdk":"flutter"}^1.9.0^1.17.2^1.0.0{"sdk":"flutter"}^4.0.0아래는 영문 원문 스냅샷입니다. 최신 내용은 GitHub에서 확인하세요.
Pub Actions Status Example Coverage License: MIT Linter GitHub stars
Octopus is a declarative router for Flutter. Its main concept and distinction from other solutions is dynamic navigation through state mutations. It is a TRULY DECLARATIVE router, where you don’t change the state imperatively using push and pop commands. Instead, you (or the user) specify the desired outcome through state mutations or the address bar, and the router delivers a predictably expected result.
Most solutions use templating for navigation, pre-describing all possible router states with hardcoding (and code generation). While this is an expected and predictable approach in traditional BE SSR (where the page is assembled server-side), it has several serious drawbacks on the client side:
/shop/category~id=1/category~id=12/category~id=123/product~id=1234).What does the current solution offer?
With a declarative approach, the only limit is your imagination!
Add the following dependency to your pubspec.yaml file:
dependencies:
octopus: <version>
Set up your routes.
You can use an enum, make a few sealed classes, or both.
This doesn't matter. A recommended and simple way is to get started with enums.
Override a builder function to link your nodes and widgets.
Optionally, set up a "title" field for any route.
enum Routes with OctopusRoute {
home('home', title: 'Home'),
gallery('gallery', title: 'Gallery'),
picture('picture', title: 'Picture'),
settings('settings', title: 'Settings');
const Routes(this.name, {this.title});
@override
final String name;
@override
final String? title;
@override
Widget builder(BuildContext context, OctopusState state, OctopusNode node) =>
switch (this) {
Routes.home => const HomeScreen(),
Routes.gallery => const GalleryScreen(),
Routes.picture => PictureScreen(id: node.arguments['id']),
Routes.settingsDialog => const SettingsDialog(),
};
}
Create an Octopus router instance.
During main initialization or state of the root App widget.
To do so, pass a list of all possible routes.
Optionally, set a defaultRoute as a route by default.
router = Octopus(
routes: Routes.values,
defaultRoute: Routes.home,
);
Add configuration from Octopus.config to the WidgetApp.router constructor.
MaterialApp.router(
routerConfig: router.config,
)
Use the context.octopus.setState((state) => ...) method as a basic navigation method.
And realize any navigation logic inside the callback as you please.
context.octopus.setState((state) =>
state
..findByName('catalog-tab')?.add(Routes.category.node(
arguments: <String, String>{'id': category.id},
)));
Of course, there are other ways to navigate, primarily shortcuts for the most common cases.
context.octopus.push(Routes.shop)
But you can truly do anything you want. Just change the state, children, nodes, and arguments as you please. Everything is in your hands and just works fine, that's a declarative approach as it should be.
Guards are a powerful tool for controlling navigation. They allow you to check the state of the router and mutate/cancel navigation if necessary. For example, you can check the user's authorization and redirect them to the login page if they are not authorized.
Examples:
State - the overall state of the router can be mutable (while the user mutates the new desired state and in guards) or immutable (all other times). The state can include a hash table of arguments, which are global arguments of the current state. These can be used at your discretion.
Node - the components that constitute the state form a tree structure in the case of nested navigation. Each node has a name and arguments (usually parameters passed to a screen, like an identifier). At each level, within each list of nodes, the combination of name and arguments must be unique, as this forms the unique key of the node.
Route - router has a list of possible routes that can be used in the project. The router matches nodes and routes by their names. Routes contain information on how to construct a page for the navigator.
Let's take a look at the next nested tree which we want to get:
Home
Shop
├─Catalog-Tab
│ ├─Catalog
│ ├─Category {id: electronics}
│ ├─Category {id: smartphones}
│ └─Product {id: 3}
└─Basket-Tab
├─Basket
└─Checkout
Also, we want the global argument shop with the value catalog to refer to a tab bar state.
Let's create the following state to represent our expectations:
final state = OctopusState(
intention: OctopusStateIntention.auto,
arguments: <String, String>{'shop': 'catalog'},
children: <OctopusNode>[
OctopusNode(
name: 'home',
arguments: <String, String>{},
children: <OctopusNode>[],
),
OctopusNode(
name: 'shop',
arguments: <String, String>{},
children: <OctopusNode>[
OctopusNode(
name: 'catalog-tab',
arguments: <String, String>{},
children: <OctopusNode>[
OctopusNode(
name: 'catalog',
arguments: <String, String>{},
children: <OctopusNode>[],
),
OctopusNode(
name: 'category',
arguments: <String, String>{'id': 'electronics'},
children: <OctopusNode>[],
),
OctopusNode(
name: 'category',
arguments: <String, String>{'id': 'smartphones'},
children: <OctopusNode>[],
),
OctopusNode(
name: 'product',
arguments: <String, String>{'id': '3'},
children: <OctopusNode>[],
),
],
),
OctopusNode(
name: 'basket-tab',
arguments: <String, String>{},
children: <OctopusNode>[
OctopusNode(
name: 'basket',
arguments: <String, String>{},
children: <OctopusNode>[],
),
OctopusNode(
name: 'checkout',
arguments: <String, String>{},
children: <OctopusNode>[],
),
],
),
],
),
],
);
Take a look closer. That's a tree structure.
Each component of that tree has a List<OctopusNode> children for children nodes and arguments for the current node.
States have arguments, too; it's your global arguments.
Each node also has a name; by this name, you can identify this node and link it with your routes table.
If we try to represent this state as a location string, we get something like that:
/home/shop/.catalog-tab/..catalog/..category~id=electronics/..category~id=smartphones/..product~id=3/.basket-tab/..basket/..checkout?shop=catalog
Refer to the Changelog to get all release notes.
If you want to support the development of our library, there are several ways you can do it:
We appreciate any form of support, whether it's a financial donation or just a star on GitHub. It helps us to continue developing and improving our library. Thank you for your support!