Flutter路由注解插件ff_annotation_route的使用

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

Flutter路由注解插件ff_annotation_route的使用

描述

ff_annotation_route 是一个用于快速生成路由映射的插件。通过注解的方式,你可以更方便地管理路由。

使用

添加依赖包到依赖项

在项目的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  # 添加此包以支持路由注解
  ff_annotation_route_core: any
  # 仅在项目中添加此包
  ff_annotation_route_library: any

然后运行以下命令来获取这些包:

flutter packages get

添加注解

空构造函数
import 'package:ff_annotation_route/ff_annotation_route.dart';

[@FFRoute](/user/FFRoute)(
  name: "fluttercandies://mainpage",
  routeName: "MainPage",
)
class MainPage extends StatelessWidget {
  // ...
}
带参数的构造函数
import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFAutoImport('hide TestMode2')
import 'package:example1/src/model/test_model.dart';
@FFAutoImport()
import 'package:example1/src/model/test_model1.dart' hide TestMode3;
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';

[@FFRoute](/user/FFRoute)(
  name: 'flutterCandies://testPageE',
  routeName: 'testPageE',
  description: 'Show how to push new page with arguments(class)',
  argumentImports: [
    'import \'package:example1/src/model/test_model.dart\';',
    'import \'package:example1/src/model/test_model1.dart\';',
  ],
  exts: {
    'group': 'Complex',
    'order': 1,
  },
)
class TestPageE extends StatelessWidget {
  const TestPageE({
    this.testMode = const TestMode(
      id: 2,
      isTest: false,
    ),
    this.testMode1,
  });

  factory TestPageE.deafult() => TestPageE(
        testMode: TestMode.deafult(),
      );

  factory TestPageE.required({[@required](/user/required) TestMode testMode}) => TestPageE(
        testMode: testMode,
      );

  final TestMode testMode;
  final TestMode1 testMode1;
}
FFRoute参数说明
参数名 描述 默认值
name 路由名称(例如 “/settings”) 必填
showStatusBar 是否显示状态栏 true
routeName 路由名称以跟踪页面 ‘’
pageRouteType 页面路由类型(material, cupertino, transparent) -
description 路由描述 ‘’
exts 扩展参数 -
argumentImports 参数导入 -
codes 支持无法写入注解的内容 -

生成路由文件

环境设置

dart 的 bin 目录添加到你的 $PATH 中:

cache\dart-sdk\bin
激活插件

运行以下命令激活插件:

dart pub global activate ff_annotation_route
执行命令

进入项目根目录并执行命令:

ff_route <command> [arguments]
命令参数

可用命令:

-h, --[no-]help 帮助使用 -p, --path Flutter项目根路径(默认为".") -n, --name 路由常量类名称(默认为"Routes") -o, --output 主项目路由文件和帮助文件的路径(相对于lib目录) -g, --git 扫描git lib(你应该指定包名并用逗号分隔多个包名) –exclude-packages 排除给定包进行扫描 –routes-file-output 路由文件的路径(相对于lib目录) –const-ignore 忽略某些路由常量的正则表达式 –[no-]package 这是一个包 –[no-]super-arguments 是否生成页面参数帮助类 -s, --[no-]save 是否保存参数到本地 它将在没有参数的情况下运行"ff_route"时执行本地参数 –[no-]null-safety 启用null-safety(默认开启) –[no-]arguments-case-sensitive 参数是否区分大小写(默认开启) –[no-]fast-mode 快速模式:仅基于单个dart文件进行分析,速度快。 非快速模式:基于整个包和sdk进行分析,支持超级参数,并自动添加参数引用导入。 (默认开启)


### Navigator 1.0
你可以在[这里](https://github.com/fluttercandies/ff_annotation_route/tree/master/example)查看完整的演示。

#### Main.dart
```dart
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/material.dart';
import 'example_route.dart';
import 'example_routes.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage,
      onGenerateRoute: (RouteSettings settings) {
        return onGenerateRoute(
          settings: settings,
          getRouteSettings: getRouteSettings,
          routeSettingsWrapper: (FFRouteSettings ffRouteSettings) {
            if (ffRouteSettings.name == Routes.fluttercandiesMainpage ||
                ffRouteSettings.name == Routes.fluttercandiesDemogrouppage.name) {
              return ffRouteSettings;
            }
            return ffRouteSettings.copyWith(
                widget: CommonWidget(
              child: ffRouteSettings.widget,
              title: ffRouteSettings.routeName,
            ));
          },
        );
      },
    );
  }
}

Push

通过名称推送
Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);
带参数的推送
Navigator.pushNamed(
  context,
  Routes.flutterCandiesTestPageE,
  arguments: <String, dynamic>{
    constructorName: 'required',
    'testMode': const TestMode(
      id: 100,
      isTest: true,
    ),
  },
);
开启–super-arguments
Navigator.pushNamed(
  context,
  Routes.flutterCandiesTestPageE.name,
  arguments: Routes.flutterCandiesTestPageE.requiredC(
    testMode: const TestMode(
      id: 100,
      isTest: true,
    ),
  ),
);

Navigator 2.0

你可以在这里查看完整的演示。

Main.dart

import 'dart:convert';
import 'package:example1/src/model/test_model.dart';
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'example1_route.dart';
import 'example1_routes.dart';

void main() {
  FFConvert.convert = <T>(dynamic value) {
    if (value == null) {
      return null;
    }
    final dynamic output = json.decode(value.toString());
    if (<int>[] is T && output is List<dynamic>) {
      return output.map<int>((dynamic e) => asT<int>(e)).toList() as T;
    } else if (<String, String>{} is T && output is Map<dynamic, dynamic>) {
      return output.map<String, String>((dynamic key, dynamic value) =>
          MapEntry<String, String>(key.toString(), value.toString())) as T;
    } else if (const TestMode() is T && output is Map<dynamic, dynamic>) {
      return TestMode.fromJson(output) as T;
    }

    return json.decode(value.toString()) as T;
  };
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final FFRouteInformationParser _ffRouteInformationParser =
      FFRouteInformationParser();

  final FFRouterDelegate _ffRouterDelegate = FFRouterDelegate(
    getRouteSettings: getRouteSettings,
    pageWrapper: <T>(FFPage<T> ffPage) {
      return ffPage.copyWith(
        widget: ffPage.name == Routes.fluttercandiesMainpage ||
                ffPage.name == Routes.fluttercandiesDemogrouppage.name
            ? ffPage.widget
            : CommonWidget(
                child: ffPage.widget,
                routeName: ffPage.routeName,
              ),
      );
    },
  );

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routeInformationProvider: PlatformRouteInformationProvider(
        initialRouteInformation: const RouteInformation(
          location: Routes.fluttercandiesMainpage,
        ),
      ),
      routeInformationParser: _ffRouteInformationParser,
      routerDelegate: _ffRouterDelegate,
    );
  }
}

FFRouteInformationParser

它在Web上工作,当你在浏览器中输入或报告到浏览器时。这是一个由[Router]小部件使用的委托,将路由信息解析为[RouteSettings]配置。

FFRouterDelegate

这是一个由[Router]小部件使用的委托,构建和配置一个导航小部件。

推送名称
FFRouterDelegate.of(context).pushNamed<void>(
  Routes.flutterCandiesTestPageA,
);
带参数的推送
FFRouterDelegate.of(context).pushNamed<void>(
  Routes.flutterCandiesTestPageF.name,
  arguments: Routes.flutterCandiesTestPageF.d(
    <int>[1, 2, 3],
    map: <String, String>{'ddd': 'dddd'},
    testMode: const TestMode(id: 1, isTest: true),
  ),
);
开启–super-arguments
FFRouterDelegate.of(context).pushNamed<void>(
  Routes.flutterCandiesTestPageF.name,
  arguments: <String, dynamic>{
    'list': <int>[1, 2, 3],
    'map': <String, String>{'ddd': 'dddd'},
    'testMode': const TestMode(id: 1, isTest: true),
  }
)

Getx

Getx支持,你只需要将FFRouteSettings转换为GetPageRoute

如何使用

void main() => runApp(const MyApp());

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage.name,
      onGenerateRoute: (RouteSettings settings) {
        FFRouteSettings ffRouteSettings = getRouteSettings(
          name: settings.name!,
          arguments: settings.arguments as Map<String, dynamic>?,
          notFoundPageBuilder: () => Scaffold(
            appBar: AppBar(),
            body: const Center(
              child: Text('not find page'),
            ),
          ),
        );
        Bindings? binding;
        if (ffRouteSettings.codes != null) {
          binding = ffRouteSettings.codes!['binding'] as Bindings?;
        }

        Transition? transition;
        bool opaque = true;
        if (ffRouteSettings.pageRouteType != null) {
          switch (ffRouteSettings.pageRouteType) {
            case PageRouteType.cupertino:
              transition = Transition.cupertino;
              break;
            case PageRouteType.material:
              transition = Transition.downToUp;
              break;
            case PageRouteType.transparent:
              opaque = false;
              break;
            default:
          }
        }

        return GetPageRoute(
          binding: binding,
          opaque: opaque,
          settings: ffRouteSettings,
          transition: transition,
          page: () => ffRouteSettings.builder(),
        );
      },
    );
  }
}

如何设置GetPageRoute的参数

例如: 'Bindings’不是const类,因此不能写在注解中,但你可以按以下代码设置:

  1. codes中定义
  2. argumentImports中添加导入URL
  3. onGenerateRoute中获取
[@FFRoute](/user/FFRoute)(
  name: "/BindingsPage",
  routeName: 'BindingsPage',
  description: 'how to use Bindings with Annotation.',
  codes: <String, String>{
    'binding': 'Bindings1()',
  },
  argumentImports: <String>[
    'import \'package:example_getx/src/bindings/bindings1.dart\';'
  ],
)
onGenerateRoute: (RouteSettings settings) {
  FFRouteSettings ffRouteSettings = getRouteSettings(
    name: settings.name!,
    arguments: settings.arguments as Map<String, dynamic>?,
    notFoundPageBuilder: () => Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text('not find page'),
      ),
    ),
  );
  Bindings? binding;
  if (ffRouteSettings.codes != null) {
    binding = ffRouteSettings.codes!['binding'] as Bindings?;
  }

  Transition? transition;
  bool opaque = true;
  if (ffRouteSettings.pageRouteType != null) {
    switch (ffRouteSettings.pageRouteType) {
      case PageRouteType.cupertino:
        transition = Transition.cupertino;
        break;
      case PageRouteType.material:
        transition = Transition.downToUp;
        break;
      case PageRouteType.transparent:
        opaque = false;
        break;
      default:
    }
  }

  return GetPageRoute(
    binding: binding,
    opaque: opaque,
    settings: ffRouteSettings,
    transition: transition,
    page: () => ffRouteSettings.builder(),
  );
},

功能化组件

如何与功能化组件一起使用?

[@swidget](/user/swidget)
[@FFRoute](/user/FFRoute)(
  name: 'flutterCandies://func1',
  routeName: 'test-func-1',
)
Widget func1(
  int a,
  String? b, {
  bool? c,
  required double d,
}) {
  return Container();
}

简单的代码可以在这里找到:这里

代码提示

你可以使用路由作为Routes.flutterCandiesTestPageE,并在IDE中查看代码提示。

默认

/// 'This is test page E.'
///
/// [name] : 'flutterCandies://testPageE'
///
/// [routeName] : 'testPageE'
///
/// [description] : 'This is test page E.'
///
/// [constructors] :
///
/// TestPageE : [TestMode testMode, TestMode1 testMode1]
///
/// TestPageE.deafult : []
///
/// TestPageE.required : [TestMode(required) testMode]
///
/// [exts] : {group: Complex, order: 1}
static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';

开启–super-arguments

/// 'This is test page E.'
///
/// [name] : 'flutterCandies://testPageE'
///
/// [routeName] : 'testPageE'
///
/// [description] : 'This is test page E.'
///
/// [constructors] :
///
/// TestPageE : [TestMode testMode, TestMode1 testMode1]
///
/// TestPageE.test : []
///
/// TestPageE.requiredC : [TestMode(required) testMode]
///
/// [exts] : {group: Complex, order: 1}
static const _FlutterCandiesTestPageE flutterCandiesTestPageE = _FlutterCandiesTestPageE();

class _FlutterCandiesTestPageE {
  const _FlutterCandiesTestPageE();

  String get name => 'flutterCandies://testPageE';

  Map<String, dynamic> d({
    TestMode testMode = const TestMode(id: 2, isTest: false),
    TestMode1 testMode1}) =>
      <String, dynamic>{
        'testMode': testMode,
        'testMode1': testMode1,
      };

  Map<String, dynamic> test() => const <String, dynamic>{
        'constructorName': 'test',
      };

  Map<String, dynamic> requiredC({[@required](/user/required) TestMode testMode}) =>
      <String, dynamic>{
        'testMode': testMode,
        'constructorName': 'requiredC',
      };

  [@override](/user/override)
  String toString() => name;
}

我可以不用它,但你必须有它

拦截器

路由拦截器

实现RouteInterceptor来根据特定场景拦截路由转换。

class LoginInterceptor extends RouteInterceptor {
  const LoginInterceptor();

  [@override](/user/override)
  Future<RouteInterceptResult> intercept(
    String routeName, {
    Object? arguments,
  }) async {
    if (!User().hasLogin) {
      return RouteInterceptResult.complete(
        routeName: Routes.fluttercandiesLoginPage.name,
      );
    }

    return RouteInterceptResult.next(
      routeName: routeName,
      arguments: arguments,
    );
  }
}
添加拦截器注解

在页面上添加拦截器注解以进行路由拦截。

[@FFRoute](/user/FFRoute)(
  name: 'fluttercandies://PageA',
  routeName: 'PageA',
  description: 'PageA',
  interceptors: [
    LoginInterceptor(),
  ],
)
class PageA extends StatefulWidget {
  const PageA({Key? key}) : super(key: key);

  [@override](/user/override)
  State<PageA> createState() => _PageAState();
}
生成映射

执行ff_route生成拦截器映射。

/// The routeInterceptors auto generated by https://github.com/fluttercandies/ff_annotation_route
const Map<String, List<RouteInterceptor>> routeInterceptors = {
  'fluttercandies://PageA': [LoginInterceptor()],
  'fluttercandies://PageB': [
    LoginInterceptor(),
    PermissionInterceptor()
  ],
};
完整配置
void main() {
  RouteInterceptorManager().addAllRouteInterceptors(routeInterceptors);
  runApp(const MyApp());
}

全局拦截器

如果你不想在注解中添加拦截器,可以选择使用全局拦截器。

实现RouteInterceptor

根据具体场景编写逻辑。

class GlobalLoginInterceptor extends RouteInterceptor {
  const GlobalLoginInterceptor();
  [@override](/user/override)
  Future<RouteInterceptResult> intercept(String routeName,
      {Object? arguments}) async {
    if (routeName == Routes.fluttercandiesPageB.name ||
        routeName == Routes.fluttercandiesPageA.name) {
      if (!User().hasLogin) {
        return RouteInterceptResult.complete(
          routeName: Routes.fluttercandiesLoginPage.name,
        );
      }
    }

    return RouteInterceptResult.next(
      routeName: routeName,
      arguments: arguments,
    );
  }
}
完整配置
void main() {
  RouteInterceptorManager().addGlobalInterceptors([
    const GlobalLoginInterceptor(),
    const GlobalPermissionInterceptor(),
  ]);
  runApp(const MyApp());
}
推送路由
  1. 使用NavigatorWithInterceptorExtension扩展调用带WithInterceptor的方法。
Navigator.of(context).pushNamedWithInterceptor(
  Routes.fluttercandiesPageA.name,
);
  1. 调用NavigatorWithInterceptor的静态方法。
NavigatorWithInterceptor.pushNamed(
  context,
  Routes.fluttercandiesPageB.name,
);

生命周期

RouteLifecycleState

通过继承RouteLifecycleState,你可以轻松检测页面的各种状态。

class _PageBState extends RouteLifecycleState<PageB> {
  [@override](/user/override)
  void onForeground() {
    print('PageB onForeground');
  }

  [@override](/user/override)
  void onBackground() {
    print('PageB onBackground');
  }

  [@override](/user/override)
  void onPageShow() {
    print('PageB onPageShow');
  }

  [@override](/user/override)
  void onPageHide() {
    print('PageB onPageHide');
  }

  [@override](/user/override)
  void onRouteShow() {
    print('onRouteShow');
  }

  [@override](/user/override)
  void onRouteHide() {
    print('onRouteHide');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page B'),
      ),
      body: GestureDetector(
        onTap: () {},
        child: const Center(
          child: Text('This is Page B'),
        ),
      ),
    );
  }
}

ExtendedRouteObserver

ExtendedRouteObserver 是一个实用类,它扩展了Flutter内置的RouteObserver的功能。它允许更高级别的路由管理和跟踪。

关键特性:

  • 跟踪导航堆栈中的所有活动路由。
  • 提供访问最顶层路由的方法。
  • 允许检查特定路由是否存在于堆栈中。
  • 允许通过名称检索路由。
  • 通知订阅者当路由被添加或移除时。
  • 支持自定义操作当路由被添加或移除时。
Widget build(BuildContext context) {
  return MaterialApp(
    navigatorObservers: [ExtendedRouteObserver()],
  );
}

全局导航器

GlobalNavigator 类是一个用于管理全局导航动作的工具类。它提供从应用程序任何位置访问NavigatorBuildContext的便捷方式。

虽然在某些情况下可以通过全局navigatorKey直接访问Navigatorcontext,但这通常不推荐,尤其是在Flutter推荐的模式下(例如通过context访问)工作良好时。

这种做法可能会引入一些潜在问题:

  1. 违背了Flutter的设计哲学:Flutter的设计基于局部导航和状态管理。绕过context的全局方法可能导致状态管理混乱,使代码难以维护。
  2. 可能导致性能问题:全局访问context可能会绕过Flutter的优化机制,因为Flutter依赖于上下文树的结构来进行高效的UI更新。
  3. 维护性差:依赖全局导航会使代码更难理解和维护,尤其是随着应用的增长。可能难以追踪导航流程和状态。
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: GlobalNavigator.navigatorKey,  
      home: HomeScreen(),
    );
  }
}
GlobalNavigator.navigator?.push(
  MaterialPageRoute(builder: (context) => SecondScreen()),
);
showDialog(
  context: GlobalNavigator.context!,
  builder: (b) {
    return AlertDialog(
      title: const Text('Permission Denied'),
      content:
          Text('You do not have permission to access this page.'),
      actions: [
        TextButton(
          onPressed: () {
            GlobalNavigator.navigator?.pop();
          },
          child: const Text('OK'),
        ),
      ],
    );
  },
);

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

1 回复

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


当然,下面是一个关于如何在Flutter项目中使用ff_annotation_route插件的示例。这个插件允许你通过注解的方式定义路由,从而简化路由管理。

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  ff_annotation_route: ^最新版本号 # 请替换为最新版本号

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

2. 配置生成代码

在项目的根目录下创建一个build.yaml文件,并添加以下内容来配置代码生成:

targets:
  $default:
    builders:
      ff_annotation_route:
        enabled: true

3. 定义路由注解

接下来,在你的Dart文件中使用注解来定义路由。例如,假设你有两个页面:HomePageSecondPage

import 'package:flutter/material.dart';
import 'package:ff_annotation_route/ff_annotation_route.dart';

// 定义HomePage路由
@FFRoute(
  name: 'homepage',
  routePage: HomePage,
)
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 导航到SecondPage
            FFRouteManager.getInstance().pushNamed('secondpage');
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

// 定义SecondPage路由
@FFRoute(
  name: 'secondpage',
  routePage: SecondPage,
)
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: Text('This is the Second Page'),
      ),
    );
  }
}

4. 初始化路由管理

在你的main.dart文件中,初始化FFRouteManager并设置初始路由。

import 'package:flutter/material.dart';
import 'package:ff_annotation_route/ff_annotation_route.dart';
import 'home_page.dart'; // 假设HomePage定义在这个文件中

void main() {
  // 初始化路由管理
  FFRouteManager.getInstance()
    ..addRoute(<String, dynamic>{
      'homepage': (settings) => HomePage(), // 可以省略,因为已经通过注解定义了
    });

  // 启动应用
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // 使用FFRouteManager作为navigator
      navigatorKey: FFRouteManager.getInstance().navigatorKey,
      initialRoute: 'homepage', // 设置初始路由
      onGenerateRoute: (settings) {
        // 通过FFRouteManager生成路由
        return FFRouteManager.getInstance().onGenerateRoute(settings);
      },
    );
  }
}

5. 运行项目

现在你可以运行你的Flutter项目,应该会看到HomePage,点击按钮后会导航到SecondPage

这个示例展示了如何使用ff_annotation_route插件通过注解定义路由,并简化路由管理。如果你有更多的页面和复杂的路由需求,可以继续使用这种方式添加更多的路由注解。

回到顶部