Flutter动态路由管理插件dynamic_routes的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter 动态路由管理插件 dynamic_routes 的使用

概览

Dynamic Routes 是一个库,它允许你在代码中预先指定哪些路由应该展示,并且以什么顺序展示。这对于基于运行时信息来控制流程非常有用。例如,在注册流程中,根据数据库中的信息(如用户是否已经注册过、居住地等),只展示必要的页面。

主要组成部分

该库主要由两个部分组成:InitiatorParticipator

  • Initiator 页面是在你希望动态导航流发生的页面之前立即出现的页面。我们将所有的导航逻辑放在这个页面上。这可以是一个登陆页面。
  • Participator 页面是数组 routes 中的页面。这些页面不包含导航逻辑,它们只知道何时进入下一页或返回上一页。

Initiator 使用示例

// landing_page.dart

class _SomeWidgetState extends State<SomeWidget> with DynamicRoutesInitiator {

  //...some code

  void onButtonPressed() {
    const isPage4Required = calculateIfPage4IsRequired();

    final routes = [
      Page1(),
      Page2(),
      Page3(),
      if (isPage4Required) Page4(),
      Page5(),
    ];

    dynamicRoutesInitiator.initializeRoutes(
        routes,
        lastPageCallback: (context) {
          // Do something; maybe return to homepage.
        }
    );

    // This will push the first Participator page.
    dynamicRoutesInitiator.pushFirst(context);
  }

//...some code

}

Participator 使用示例

// page1.dart

class _SomeWidgetState extends State<SomeWidget> with DynamicRoutesParticipator {
  // pushNext tells this participator page to push the next page in the routes array we saw earlier.
  void onNextButtonPressed() => dynamicRoutesParticipator.pushNext(context);
  // popCurrent tells this participator page to pop the current page and all of its sub-routes.
  // In some cases, this is the same as using Navigator.of(context).pop(context)
  void onBackButtonPressed() => dynamicRoutesParticipator.popCurrent(context);

//...build methods and whatever
}

关于 popCurrent

popCurrent 的行为类似于大多数情况下使用 Navigator.of(context).pop。除非必要,否则应使用 popCurrent,因为它绑定在 NavigationLogicProvider 上,可以被覆盖(见扩展导航逻辑部分)。此外,popCurrent 可以确保当前页面被弹出,而不是顶部的页面。

处理 Initiator 和 Participator 的销毁

我们可以调用 Initiatordispose 方法来销毁实例及其页面。这也会销毁所有 Participator 实例。

[@override](/user/override)
void dispose() {
  dynamicRoutesInitiator.dispose();

  super.dispose();
}

嵌套导航

你可以有一个子路由导航,例如,Initiator 数组中的第二个成员也可以是一个 Initiator 并分支到自己的动态导航流。

class _MixedPageState extends State<MixedPage>
    with DynamicRoutesParticipator, DynamicRoutesInitiator {
  // Some code
}
Widget buildButtons() {
  return Column(
      children: [
        TextButton(
            child: Text("Click this to branch off"),
            onPressed: () {
              dynamicRoutesInitiator.initializeRoutes(const [
                ParticipatorPage(title: "SubFlow 1 Sub page 1"),
                ParticipatorPage(title: "SubFlow 1 Sub page 2"),
                ParticipatorPage(title: "SubFlow 1 Sub page 3"),
              ], lastPageCallback: (context) {
                dynamicRoutesInitiator.popUntilInitiatorPage(context);

                // Or if you do this, this page, and all of the pages in the subflow that branched off
                // from this page, will be popped. Internally, we're using popUntil
                // dynamicRoutesParticipator.popCurrent(context);
              });
            }
        ),
        TextButton(
          child: Text("Click this to continue the flow"),
          onPressed: () => dynamicRoutesParticipator.pushNext(context),
        )
      ]
  );
}

双重嵌套导航

你还可以在一个子流内再嵌入另一个子流。

Widget buildButtons() {
  return TextButton(
      child: Text("Click this to branch off"),
      onPressed: () {
        dynamicRoutesInitiator.initializeRoutes(const [
          // Where SubflowPage class is both a navigator and an initiator.
          SubflowPage(pages: [
            Page1(),
            Page2(),
            Page3(),
            SubflowPage(pages: [
              Page1(),
              if (page2Required) Page2(),
              if (page4BeforePage3) ...[Page4(), Page3()] else
                [
                  Page3(),
                  Page4(),
                ]
            ])
          ]),
          ParticipatorPage(title: "SubFlow 1 Sub page 3"),
        ], lastPageCallback: (context) {
          // Do whatever
        });
      }
  );
}

多页面导航

pushFor

你可以一次推送多个页面。

// Pushes 4 pages.
dynamicRoutesParticipator.pushFor(context, 4);

// Pushes to the last participator page.
dynamicRoutesParticipator.pushFor(context, dynamicRoutesParticipator..getProgressFromCurrentPage());

// Pushes to the last participator page + invoke [lastPageCallback].
dynamicRoutesParticipator.pushFor(context, dynamicRoutesParticipator..getProgressFromCurrentPage() + 1);
pushFirstThenFor

这是 pushFor 的类似功能,但由 Initiator 调用。内部实现为 pushFirstpushFor

dynamicRoutesInitiator.initializeRoutes(...);
// This will push the first page, then push 3 more pages. We are basically pushing a total of 4 pages.
final results = await Future.wait(dynamicRoutesInitiator.pushFirstThenFor(context, 3));

print(results); //[resultFromFirst, resultFromSecond, resultFromThird, resultFromFourth]
popFor

你可以重置流,例如回到第一个 Participator 页面或 Initiator 页面。

// Pop just 2 pages while returning true as the result to those two pages.
dynamicRoutesNavigator.popFor(context, 2 , true);

// This pops until the first participator page.
final currentPageIndex = dynamicRoutesNavigator.getCurrentPageIndex();
dynamicRoutesNavigator.popFor(context, currentPageIndex);

// Add + 1 to currentPageIndex or just use double.infinity to pop to the Initiator page.
dynamicRoutesNavigator.popFor(context, currentPageIndex);
dynamicRoutesNavigator.popFor(context, double.infinity);

缓存

该库还支持简单的缓存方法。

void saveToCache(WhatEverClassThisThingIs someData) {
  dynamicRoutesParticipator.setCache(someData);

  // Or

  dynamicRoutesInitiator.setCache(someData);
}
Whatever readFromCache() {
  return dynamicRoutesParticipator.getCache() as Whatever;
}

// Or

Whatever readFromCache() {
  return dynamicRoutesInitiator.getCache() as Whatever;
}

默认情况下,缓存数据会在调用 dispose 方法时被清除。可以通过 clearCache 参数直接覆盖。

[@override](/user/override)
void initState() {
  dynamicRoutesInitiator.dispose(clearCache: false); // true by default.

  super.initState();
}

修改、扩展或替换导航逻辑

你可以部分或完全替代或修改导航逻辑。例如,如果你想在每次调用 pushNextpop 时执行某些操作,可以实现 NavigationLogicProvider 类或其实施,并将其作为新的 navigationLogicProvider 提供。

// Create a new class that extends NavigationLogicProvider.
class CustomNavigationLogicProvider implements NavigationLogicProvider {
  final Function(Widget) customNextCallback;
  final Function(Widget?) customBackCallback;

  const CustomNavigationLogicProvider(
      {required this.customNextCallback, required this.customBackCallback});

  [@override](/user/override)
  void back<T>(args) {
    customBackCallback(args.previousPage);
  }

  [@override](/user/override)
  Future<T?> next<T>(args) async {
    customNextCallback(args.nextPage);

    return null;
  }
}
void initiateDynamicRoutesInstane() {
  // Initialize normally
  dynamicRoutesInitiator.initializeRoutes(_widgets,
      lastPageCallback: (newContext) {
        dynamicRoutesInitiator.popUntilInitiatorPage(context);
      });

  final customNavigationLogicProvider = CustomNavigationLogicProvider(
      customNextCallback: (Widget widget) {
        setState(() {
          _displayedWidget = widget;
        });
      }, customBackCallback: (Widget? maybeAWidget) {
    setState(() {
      _displayedWidget = maybeAWidget;
    });
  });

  // Again, make sure this is called after initializeRoutes.
  dynamicRoutesInitiator.setNavigationLogicProvider(_customNavigationLogicProvider);

  dynamicRoutesInitiator.pushFirst(context);
}

示例代码

import 'package:dynamic_routes/dynamic_routes/mixins/initiator.dart';
import 'package:example/pages/mixed_page/mixed_page.dart';
import 'package:example/pages/participator_page.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic Routes Test',
      theme: ThemeData(
        primarySwatch: Colors.pink,
      ),
      // One might argue: "What's new with this? We can already do this using
      // the normal Navigator class".
      //
      // Well, that's right, but what you can't do is that do exactly this, but
      // deciding which page to be shown
      //
      // or which place to be swapped at runtime, all from just one place without
      // the pages even knowing where they are going,
      // the only thing they do is pushing the next thing in the array.
      home: MyHomePage(title: 'Dynamic Routes Test', pageWidgets: [
        const ParticipatorPage(title: "Page 1"),
        const ParticipatorPage(title: "Page 2"),
        // A page that can both continue the flow and branch off into a new flow.
        // In real applications, you'd of course use route predicates to pop back
        // to something.
        //
        // For simplicity's sake, we'll just do pop multiple times for this example.
        MixedPage(
          // This isSubSubFlow has nothing to do with the library, it's just that
          // I'm too lazy to change the title of subsubflow pages.
          isSubSubFlow: false,
          subFlowSet1: const [
            ParticipatorPage(title: "SubFlow 1 Sub page 1"),
            ParticipatorPage(title: "SubFlow 1 Sub page 2"),
            ParticipatorPage(title: "SubFlow 1 Sub page 3"),
          ],
          subFlowSet1Callback: (context) {
            Navigator.of(context).pop();
            Navigator.of(context).pop();
            Navigator.of(context).pop();
          },
          subFlowSet2: [
            const ParticipatorPage(title: "SubFlow 1 Sub page 1"),
            const ParticipatorPage(title: "SubFlow 1 Sub page 2"),
            const ParticipatorPage(title: "SubFlow 1 Sub page 3"),
            // A sub flow within sub flow....sub-ception!
            MixedPage(
                // This isSubSubFlow has nothing to do with the library, it's
                // just that I'm too lazy to change the title of subsubflow pages.
                isSubSubFlow: true,
                subFlowSet1: const [
                  ParticipatorPage(title: "SubSubflow 1 Sub Page 1"),
                  ParticipatorPage(title: "SubSubflow 1 Sub Page 2"),
                ],
                subFlowSet1Callback: (context) {
                  Navigator.of(context).pop();
                  Navigator.of(context).pop();
                },
                subFlowSet2: const [
                  ParticipatorPage(title: "SubSubflow 2 Sub page 1"),
                  ParticipatorPage(title: "SubSubflow 2 Sub page 2"),
                ],
                subFlowSet2Callback: (context) {
                  Navigator.of(context).pop();
                  Navigator.of(context).pop();
                }),
            const ParticipatorPage(title: "SubFlow 1 Sub page 5"),
          ],
          subFlowSet2Callback: (context) {
            Navigator.of(context).pop();
            Navigator.of(context).pop();
            Navigator.of(context).pop();
            Navigator.of(context).pop();
            Navigator.of(context).pop();
          },
        ),
        const ParticipatorPage(title: "Page 4"),
        const ParticipatorPage(title: "Page 5"),
        const ParticipatorPage(title: "Page 6"),
      ]),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final List<Widget> pageWidgets;

  const MyHomePage({
    Key? key,
    required this.title,
    required this.pageWidgets,
  }) : super(key: key);

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with DynamicRoutesInitiator {
  late List<Widget> _widgets = widget.pageWidgets;

  [@override](/user/override)
  void dispose() {
    dynamicRoutesInitiator.dispose();

    super.dispose();
  }

  [@override](/user/override)
  void initState() {
    dynamicRoutesInitiator.setCache(0);

    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final value = dynamicRoutesInitiator.getCache();
    return Scaffold(
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisSize: MainAxisSize.min,
        children: [
          ElevatedButton(
            child: const Text("Shuffle page order"),
            onPressed: () {
              final newWidgets = [..._widgets]..shuffle();
              _widgets = newWidgets;
            },
          ),
          ElevatedButton(
              onPressed: () =>
                  setState(() => dynamicRoutesInitiator.setCache(value + 1)),
              child: Text("Increment cached value: $value")),
        ],
      ),
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: const Center(child: Text("Dynamic Routes test")),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(16),
        child: TextButton(
          child: const Text("Enter flow"),
          onPressed: () {
            dynamicRoutesInitiator.initializeRoutes(_widgets,
                lastPageCallback: (newContext) {
              dynamicRoutesInitiator.popUntilInitiatorPage(context);
            });

            dynamicRoutesInitiator.pushFirst(context).then((_) {
              // Call setState to refresh the displayed cached value.
              setState(() {});
            });
          },
        ),
      ),
    );
  }
}

更多关于Flutter动态路由管理插件dynamic_routes的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动态路由管理插件dynamic_routes的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,dynamic_routes 是 Flutter 中一个用于动态路由管理的插件。它允许开发者在应用运行时动态地注册和导航到新的路由,而无需在编译时静态地定义所有路由。这对于需要根据用户权限或运行时条件动态生成路由的应用非常有用。

以下是一个使用 dynamic_routes 插件的简单示例。在这个示例中,我们将展示如何安装和使用该插件来动态管理路由。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 dynamic_routes 依赖:

dependencies:
  flutter:
    sdk: flutter
  dynamic_routes: ^x.y.z  # 请替换为最新版本号

然后运行 flutter pub get 来获取依赖。

2. 配置 MaterialApp

在你的 main.dart 文件中,配置 MaterialApp 以使用 DynamicRouterDelegateDynamicRouteInformationParser

import 'package:flutter/material.dart';
import 'package:dynamic_routes/dynamic_routes.dart';
import 'routes/dynamic_routes_delegate.dart'; // 假设你将路由逻辑放在这个文件

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: DynamicRouteInformationParser<String>(
        initialRoute: '/',
      ),
      routerDelegate: MyDynamicRouterDelegate(),
    );
  }
}

3. 创建 DynamicRouterDelegate

创建一个新的 Dart 文件(例如 routes/dynamic_routes_delegate.dart),并在其中定义 MyDynamicRouterDelegate 类。

import 'package:flutter/material.dart';
import 'package:dynamic_routes/dynamic_routes.dart';
import 'pages/home_page.dart'; // 假设你的页面在这个目录
import 'pages/detail_page.dart'; // 另一个示例页面

class MyDynamicRouterDelegate extends DynamicRouterDelegate<String>
    with ChangeNotifier {
  @override
  final Map<String, DynamicRoutePageBuilder> routes = {
    '/': (context, settings) => HomePage(),
    // 初始路由
  };

  // 用于动态添加路由的方法
  void addDetailRoute(String id) {
    routes['/detail/$id'] = (context, settings) => DetailPage(id: id);
    notifyListeners(); // 通知监听器路由已更改
  }

  @override
  String get currentConfiguration {
    // 返回当前路由路径作为配置标识符
    final RouteInformation? routeInformation = ModalRoute.of(navigatorKey.currentContext)?.routeInformation;
    return routeInformation?.location ?? '/';
  }

  @override
  GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
}

4. 创建页面

创建你的页面组件,例如 home_page.dartdetail_page.dart

home_page.dart

import 'package:flutter/material.dart';
import '../routes/dynamic_routes_delegate.dart';

class HomePage extends StatelessWidget {
  final MyDynamicRouterDelegate routerDelegate;

  HomePage({required this.routerDelegate});

  void navigateToDetail(String id) {
    routerDelegate.addDetailRoute(id); // 动态添加新路由
    Navigator.of(context).pushNamed('/detail/$id'); // 导航到新路由
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => navigateToDetail('123'), // 示例ID
          child: Text('Go to Detail Page'),
        ),
      ),
    );
  }
}

detail_page.dart

import 'package:flutter/material.dart';

class DetailPage extends StatelessWidget {
  final String id;

  DetailPage({required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail Page')),
      body: Center(
        child: Text('Detail Page for ID: $id'),
      ),
    );
  }
}

5. 运行应用

现在,你可以运行你的 Flutter 应用。在 HomePage 上点击按钮将动态添加一个新路由并导航到 DetailPage

这个示例展示了如何使用 dynamic_routes 插件在 Flutter 应用中动态管理路由。根据你的具体需求,你可以进一步扩展和定制这个基础实现。

回到顶部