APS Navigator - 应用分页系统

Navigator 2.0 和 Router/Pages 的封装,以简化它们的使用。

该库只是Navigator 2.0和Router/Pages API的一个包装器,试图使它们更易于使用。

基本功能集

我们试图实现什么

  • 简单的API
  • 易于设置
  • 需要学习的“新类类型”数量最少
    • 无需扩展(或实现)任何内容
  • Web支持(请参阅后续部分中的图片)
    • 后退/前进按钮
    • 动态URL
    • 静态URL
    • 从Web历史记录恢复应用程序状态
  • 路由堆栈控制
    • 在特定位置添加/删除页面
    • 一次添加多个页面
    • 一次删除一个页面范围
  • 处理操作系统事件
  • 内部(嵌套)导航器

我们试图不实现什么

  • 使用代码生成
    • 别误会我。代码生成是一项很棒的技术,可以使代码清晰且编码速度更快——我们社区中有使用它的优秀库。
    • 问题是:对于像导航这样“基本”的东西,使用这种程序对我来说似乎不自然。
  • 使用强类型参数传递

概述

1 - 创建Navigator并定义路由

final navigator = APSNavigator.from(
  routes: {
    '/dynamic_url_example{?tab}': DynamicURLPage.route,  
    '/': ...
  },
);

2 - 配置MaterialApp以使用它

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: navigator,
      routeInformationParser: navigator.parser,
    );
  }
}

3 - 创建widget Page(路由)

class DynamicURLPage extends StatefulWidget {
  final int tabIndex;
  const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);

  @override
  _DynamicURLPageState createState() => _DynamicURLPageState();

  // Builder function
  static Page route(RouteData data) {
    final tab = data.values['tab'] == 'books' ? 0 : 1;
    return MaterialPage(
      key: const ValueKey('DynamicURLPage'), // Important! Always include a key
      child: DynamicURLPage(tabIndex: tab),
    );
  }
}
  • 您无需使用静态函数作为PageBuilder,但这似乎是组织事物的不错方式。
  • 重要提示:**避免**在MaterialPageDynamicURLPage级别使用“const”关键字,否则“Pop”可能无法与Web历史记录正常工作。
  • 重要提示:**始终**包含一个Key。

4 - 导航到它

 APSNavigator.of(context).push(
    path: '/dynamic_url_example',
    params: {'tab': 'books'},
 );
  • 浏览器地址栏将显示:/dynamic_url_example?tab=books
  • 将创建Page并将其放在路由堆栈的顶部。

以下各节将更好地描述上述步骤。

用法

1 - 创建Navigator并定义路由

final navigator = APSNavigator.from(

  // Defines the initial route - default is '/':
  initialRoute: '/dynamic_url_example', 

  //  Defines the initial route params - default is 'const {}':
  initialParams: {'tab': '1'},

  routes: {
    // Defines the location: '/static_url_example'
    '/static_url_example': PageBuilder..,

    // Defines the location (and queries): '/dynamic_url_example?tab=(tab_value)&other=(other_value)'
    // Important: Notice that the '?' is used only once 
    '/dynamic_url_example{?tab,other}': PageBuilder..,

    // Defines the location (and path variables): '/posts' and '/posts/(post_id_value)'
    '/posts': PageBuilder..,
    '/posts/{post_id}': PageBuilder..,

    // Defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)&q2=(q2_value)'.
    '/path/{id}?{?q1,q2}': PageBuilder..,

    // Defines app root - default
    '/': PageBuilder..,
  },
);

routes只是TemplatesPage Builders之间的映射。

  • Templates是简单的字符串,其中包含用于路径({a})和查询({?a,b,c..})值的预定义标记。
  • Page Builders是返回Page并接收RouteData的普通函数。请参阅下面的第3部分。

根据以上配置,应用程序将打开:/dynamic_url_example?tab=1

2 - 配置MaterialApp

创建Navigator后,我们需要将其设置为已使用。

  • 将其设置为MaterialApp.router.routeDelegate

  • 请记住也要添加MaterialApp.router.routeInformationParser

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

    @override
    Widget build(BuildContext context) {
      return MaterialApp.router(
        routerDelegate: navigator,
        routeInformationParser: navigator.parser,
      );
    }
    

    }

3 - 创建widget Page(路由)

构建Page

  • 该库会尝试将地址templates与当前地址进行匹配。例如。

    • 模板:/dynamic_url_example/{id}{?tab,other}'
    • 地址:/dynamic_url_example/10?tab=1&other=abc
  • 所有路径查询值都将被提取并包含在RouteData.data实例中。例如。

    • {'id': '10', 'tab': '1', 'other': 'abc'}
  • 此实例将作为参数传递给PageBuilder函数——static Page route(RouteData data)...

  • 将创建一个新的Page实例并包含在路由堆栈中——您可以通过开发工具轻松检查。

    class DynamicURLPage extends StatefulWidget {
    final int tabIndex;
    const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);

    @override
    _DynamicURLPageState createState() => _DynamicURLPageState();
    
    // You don't need to use a static function as Builder, 
    // but it seems to be a good way to organize things   
    static Page route(RouteData data) {
      final tab = data.values['tab'] == 'books' ? 0 : 1;
      return MaterialPage(
        key: const ValueKey('DynamicURLPage'), // Important! Always include a key
        child: DynamicURLPage(tabIndex: tab),
      );
    }
    

    }

4 - 导航到Pages

示例链接:所有导航示例

4.1 - 导航到带有查询变量的路由

  • 模板:/dynamic_url_example{?tab,other}

  • 地址:/dynamic_url_example?tab=books&other=abc

    APSNavigator.of(context).push(
    path: '/dynamic_url_example',
    params: {'tab': 'books', 'other': 'abc'}, // 在[params]中添加查询值
    );

4.2 - 导航到带有路径变量的路由

  • 模板:/posts/{post_id}

  • 地址:/posts/10

    APSNavigator.of(context).push(
    path: '/post/10', // 在[path]中设置路径值
    );

4.3 - 您也可以包含不作为查询变量使用的参数

  • 模板:/static_url_example

  • 地址:/static_url_example

    APSNavigator.of(context).push(
    path: '/static_url_example',
    params: {'tab': 'books'}, // 它将被添加到[RouteData.values['tab']]
    );

详情

1. 动态URL示例

示例链接:动态URL示例

dynamic_url_example

使用动态URL时,更改应用程序状态也会更改浏览器的URL。为此

  • 在模板中包含查询。例如:/dynamic_url_example{?tab}

  • 调用updateParams方法以更新浏览器的URL。

    final aps = APSNavigator.of(context);
    aps.updateParams(
      params: {'tab': index == 0 ? 'books' : 'authors'},
    );
    
  • 以上方法将在浏览器历史记录中包含一个新条目。

  • 稍后,如果用户选择此类条目,我们可以使用以下方法恢复之前的widget State

    @override
    void didUpdateWidget(DynamicURLPage oldWidget) {
      super.didUpdateWidget(oldWidget);
      final values = APSNavigator.of(context).currentConfig.values;
      tabIndex = (values['tab'] == 'books') ? 0 : 1;
    }
    

重要知识点

  • 当前限制:URL中使用的任何值都必须保存为string
  • 不要忘记为PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。

2. 静态URL示例

示例链接:静态URL示例

static_url_example

使用静态URL时,更改应用程序状态不会更改浏览器的URL,但它会生成历史记录中的一个新条目。为此

  • 请勿在路由模板中包含查询。例如:/static_url_example

  • 就像我们用动态URL所做的一样,再次调用updateParams方法。

    final aps = APSNavigator.of(context);
    aps.updateParams(
      params: {'tab': index == 0 ? 'books' : 'authors'},
    );
    
  • 然后,允许从浏览器历史记录中还原State

    @override
    void didUpdateWidget(DynamicURLPage oldWidget) {
      super.didUpdateWidget(oldWidget);
      final values = APSNavigator.of(context).currentConfig.values;
      tabIndex = (values['tab'] == 'books') ? 0 : 1;
    }
    

重要知识点

  • 不要忘记为PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。

3. 返回数据示例

示例链接:返回数据示例

return_data_example

推送一个新路由并等待结果。

  final selectedOption = await APSNavigator.of(context).push(
     path: '/return_data_example',
  );

Pop返回数据。

  APSNavigator.of(context).pop('Do!');

重要知识点

  • 数据将只返回一次。

  • 如果用户通过浏览器历史记录导航您的应用程序并再次返回,结果将在didUpdateWidget方法中以result,的形式返回,而不是通过await调用。

    @override
    void didUpdateWidget(HomePage oldWidget) {
      super.didUpdateWidget(oldWidget);
      final params = APSNavigator.of(context).currentConfig.values;
      result = params['result'] as String;
      if (result != null) _showSnackBar(result!);
    }
    

4. 多次推送

示例链接:多次推送示例

multi_push_example

一次推送一个Page列表。

  APSNavigator.of(context).pushAll(
    // position: (default is at top)
    list: [
      ApsPushParam(path: '/multi_push', params: {'number': 1}),
      ApsPushParam(path: '/multi_push', params: {'number': 2}),
      ApsPushParam(path: '/multi_push', params: {'number': 3}),
      ApsPushParam(path: '/multi_push', params: {'number': 4}),
    ],
  );

在上面的示例中,ApsPushParam(path: '/multi_push', params: {'number': 4}),将是新的顶部。

重要知识点

  • 您不必一定将其添加到顶部;您可以使用position参数将其添加到路由堆栈的中间。
  • 不要忘记为PageBuilder创建的Page包含一个Key,以使所有内容都能正常工作。

5. 多次移除

示例链接:多次移除示例
multi_remove_example

根据范围删除所有您想要的Page。

  APSNavigator.of(context).removeRange(start: 2, end: 5);

6. 内部(嵌套)导航器

示例链接:内部导航器示例

internal_nav_example-1

class InternalNavigator extends StatefulWidget {
  final String initialRoute;

  const InternalNavigator({Key? key, required this.initialRoute})
      : super(key: key);

  @override
  _InternalNavigatorState createState() => _InternalNavigatorState();
}

class _InternalNavigatorState extends State<InternalNavigator> {
  late APSNavigator childNavigator = APSNavigator.from(
    parentNavigator: navigator,
    initialRoute: widget.initialRoute,
    initialParams: {'number': 1},
    routes: {
      '/tab1': Tab1Page.route,
      '/tab2': Tab2Page.route,
    },
  );

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    childNavigator.interceptBackButton(context);
  }

  @override
  Widget build(BuildContext context) {
    return Router(
      routerDelegate: childNavigator,
      backButtonDispatcher: childNavigator.backButtonDispatcher,
    );
  }
}

重要知识点

  • 当前限制:浏览器URL不会根据内部导航器状态进行更新。

GitHub

https://github.com/guilherme-v/aps_navigator