Flutter 正则匹配实战:数据篇 您所在的位置:网站首页 js正则匹配日期 Flutter 正则匹配实战:数据篇

Flutter 正则匹配实战:数据篇

2023-06-15 07:54| 来源: 网络整理| 查看: 265

目录

一、关联正则功能介绍

1. 需求分析

2. 右侧导航栏

二、关联正则的数据层

1. 数据实体类与数据库升级

2. 数据操作层:LinkRegexDao

3.数据存储:LinkRegexRepository

三、业务逻辑层与视图层的实现

1.关联正则的状态类

2.关联正则的业务逻辑

3.视图层组件构建

4.事件的处理

5. 保存正则功能

一、关联正则功能介绍

先来看一下我们期望实现什么需求:如下所示,在右侧面板中有 关联正则表达式 面板。左侧每个 记录条目 都可以对于若干个关联正则,在数据层面也就是 一对多 的关系。

可以点击复选框,激活某一个正则条目。该操作会造成输入框内容的变化,然后影响匹配内容。在切换记录时,关联正则表达式 面板会进行对应更新:

1. 需求分析

这个功能中,状态间的依赖关系比较复杂。如下图中,1 处切换,会影响 4 和 2 内容; 2 中的选择,会影响 3 的内容,并重新匹配,影响 4 的内容:

下面是从数据的角度,来看需求之间的关系:

前面需求的中相关的数据通过状态类持有,而状态中的数据通过对应的 XXXBloc 维护。也就是说,数据之间的关系,最终反映为 Bloc 之间的关系。在新增的这个 关联正则表达式 需求中,可以很好地说明如何处理 Bloc 之间的关系。

2. 右侧导航栏

由于这里期望 记录面板 和 关联正则面板 可以同时打开,所以先解决一下右侧导航栏构建的问题。有了左侧导航栏的经验,其实右侧也很类似。如下图所示:

为了支持右栏,首先需要拓展一下 NavTab 数据,如下所示添加一个 NavType 的成员,用于表示导航页签的类型,现在只有左右两个:

enum NavType{ left, right, } class NavTab { final int id; final String name; final NavType type; final IconData icon; final bool down; const NavTab({ required this.name, required this.icon, this.type = NavType.left, required this.id, this.down = false, }); }

然后正在 Cons 静态常量 navTabs 中,添加右侧类型的页签:

在主页面构建中,将右侧部分填入即可。这里根据语境,将页签栏更名:

LeftTabNavigation --> RailTabNavigation

这里向 RailTabNavigation 中提供 textDirection 属性,表示排列的方式。对于右栏来说,是自右向左排列。另外,由于左右两侧的导航栏,运行同时激活,自然需要维护两个激活索引。

RailTabNavigation( width: 22, textDirection: TextDirection.rtl, activeId: activeRightNavId, onSelect: _onSelectNav, items: rightTabs, ),

由于左右导航处理类似,这里就不赘述了。

二、关联正则的数据层

一切的需求,都是建立在 数据 的基础上,维护数据的业务逻辑是程序能够运转的基石。对于匹配需求来说,数据层是 RegexParser,用于获取匹配数据;对于记录需求来说,数据层是 RecoderRepository,用于支持记录数据的增删改查操作:

同样,对于 关联正则表达式 需求也需要数据层进行维护。这里 关联正则 也在数据库中进行存储,所以和 记录数据 的操作是很类似的:

1. 数据实体类与数据库升级

关联正则的一条记录对应的字段如下,主要就是记录一条正则表达式信息。另外每条 关联正则 通过 record_id 来表示其所属的 正则记录 。这里期望和 record_id 关联的正则表达式不要重复,可以通过 UNIQUE 来指定联合的唯一索引:

---->[repository/impl/db/model/link_regex.dart]---- class LinkRegex{ // 关联正则表 static const String tableSql = """ CREATE TABLE `link_regex` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `regex` TEXT , `record_id` INTEGER, `timestamp` INTEGER, UNIQUE(record_id,regex) ) """; final int id; final String regex; final int recordId; final int timestamp;

前面在数据库的使用中介绍了数据库升级。现在想在 第三版 数据库中添加一张数据表,只需要在 DbUpdater 中做如下三件事:

2. 数据操作层:LinkRegexDao

这里关联正则的展示,在记录切换时,只要根据指定 recordId 查询关联的正则即可;由于关联的记录数不会非常多,这里就不用分页了。下面是从数据库中对 link_regex 表进行增删改查的代码:

class LinkRegexDao { final Database _database; LinkRegexDao(this._database); Future queryLinkRegexByRecordId(int recordId) { return _database.query('link_regex', where: 'record_id = ?', whereArgs: [recordId], orderBy: "timestamp DESC"); } Future insert(LinkRegex data) => _database.insert( 'link_regex', data.toJson(), conflictAlgorithm: ConflictAlgorithm.replace, ); Future deleteById(int id) => _database.delete( 'link_regex', where: "id = ?", whereArgs: [id], ); Future update(LinkRegex data) => _database.update( 'link_regex', data.toJson(), where: "id = ?", whereArgs: [data.id], ); }

别忘了在 LocalDb 中提供 LinkRegexDao 的访问方式,并在数据库打开时,实例化 LinkRegexDao 对象:

FutureOr _onOpen(Database db) { print('数据库打开....'); _recoderDao = RecoderDao(db); _linkRegexDao = LinkRegexDao(db); } 3.数据存储:LinkRegexRepository

这里仍是通过定义 Repository 接口,提供数据的访问功能,再给出具体的实现类。关联正则的数据仓储访问接口,如下所示:

abstract class LinkRegexRepository { /// 查询关联正则 Future queryLinkRegexByRecordId({ required int recordId, }); /// 插入关联正则 Future insert(LinkRegex record); /// 根据 [id] 删除关联正则 Future deleteById(int id); /// 修改关联正则 Future update(LinkRegex record); }

然后通过 DbLinkRegexRepository 实现数据库方式维护数据的仓储实现类。核心的数据库操作功能,由 LinkRegexDao 提供:

class DbLinkRegexRepository implements LinkRegexRepository{ const DbLinkRegexRepository(); LinkRegexDao get dao => LocalDb.instance.linkRegexDao; @override Future deleteById(int id) => dao.deleteById(id); @override Future insert(LinkRegex record) => dao.insert(record); @override Future queryLinkRegexByRecordId({required int recordId}) async{ List result = await dao.queryLinkRegexByRecordId(recordId); return result.map(LinkRegex.fromJson).toList(); } @override Future update(LinkRegex record) => dao.update(record); }

这样,关联正则 在数据层的工作就准备完毕,下面进入视图模型层(业务逻辑层),也就是完成 LinkRegexBloc 的代码;以及视图层的界面展示。

三、业务逻辑层与视图层的实现

对于关联正则而言,需要维护的核心数据有如下两个。这其实和前面的记录是类似的,都是列表数据和激活数据。

1.关联正则的状态类

同样,对于关联正则面板,也要根据不同的状态展示不同的界面。所以仿照之前记录状态,定义如下:

abstract class LinkRegexState extends Equatable { const LinkRegexState(); @override List get props => []; } class ErrorLinkRegexState extends LinkRegexState { final String error; const ErrorLinkRegexState({required this.error}); @override List get props => [error]; } class EmptyLinkRegexState extends LinkRegexState { const EmptyLinkRegexState(); } class LoadingLinkRegexState extends LinkRegexState { const LoadingLinkRegexState(); }

最主要的还是 LoadedLinkRegexState,其中存储 LinkRegex 列表和激活 id 数据:

class LoadedLinkRegexState extends LinkRegexState { final List regexes; final int activeLinkRegexId; const LoadedLinkRegexState({ required this.regexes, required this.activeLinkRegexId, }); @override List get props => [activeLinkRegexId, regexes]; } 2.关联正则的业务逻辑

由于关联正则数据不会很多,没有分页查询;以及在每次记录切换时都要重新查询。所以这里就没有刷新、加载更多之类的操作类型。在 loadLinkRegex 方法中,根据 recordId 使用仓储查询记录即可:

class LinkRegexBloc extends Cubit { final LinkRegexRepository repository = const DbLinkRegexRepository(); LinkRegexBloc() : super(const EmptyLinkRegexState()); void loadLinkRegex({ required int recordId, }) async { LinkRegexState state; emit(const LoadingLinkRegexState()); List results = []; try { results = await repository.queryLinkRegexByRecordId( recordId: recordId ); if (results.isNotEmpty) { state = LoadedLinkRegexState( activeLinkRegexId: results.first.id, regexes: results, ); } else { state = const EmptyLinkRegexState(); } } catch (e) { debugPrint(e.toString()); state = ErrorLinkRegexState(error: e.toString()); } emit(state); } void select(int id) { emit(state.copyWith(activeLinkRegexId: id)); } } 3.视图层组件构建

面板通过 BlocBuilder 响应 LinkRegexState 的状态变化,根据不同的状态类型,构建不同的视图表现,效果如下。

加载中无数据有数据异常

class LinkRegexPanel extends StatelessWidget { const LinkRegexPanel({super.key}); @override Widget build(BuildContext context) { return Column( children: [ const LinkRegexTopBar(), Expanded( child: BlocBuilder( builder: (_, state) => _buildByState(state), ), ) ], ); } Widget _buildByState(LinkRegexState state) { if (state is LoadingLinkRegexState) { return const LoadingPanel(); } if (state is EmptyLinkRegexState) { return const EmptyPanel( data: "暂无关联正则", icon: TolyIcon.icon_empty_panel, ); } if (state is ErrorLinkRegexState) { return ErrorPanel( data: "数据查询异常", icon: TolyIcon.zanwushuju, error: state.error, ); } if (state is LoadedLinkRegexState) { return LoadedRegexPanel( state: state, ); } return const SizedBox(); } } 4.事件的处理

最后来看一下事件的处理:首先最重要的是条目的点击激活事件,注意这里点击选中,如果已经选中,则会取消选中:

选中时的逻辑如下,选中条目通过 LinkRegexBloc#select 方法进行处理,产出新状态。 如果已经选中,将激活 id 赋为 -1 ,表示不激活。如果未选中,将点击条目对应的 id 激活:

---->[loaded_regex_panel.dart]---- void _onSelect(BuildContext context,LinkRegex regex) { if(regex.id == state.activeLinkRegexId){ context.read().select(-1); }else{ context.read().select(regex.id); } }

然后看一下,在 1 激活关联正则时,如何影响 2 输入框的值,并且触发重新匹配,影响 3 的内容:

对于 flutter_bloc 状态管理,有一个非常好用的利器:通过 BlocListener 可以监听某个 Bloc 中的状态变化,从而做出响应。很明显,这里只需要监听 LinkRegexState 的变化,然后为输入框的控制器赋值即可:

这里将正则输入框单独封装为 RegexInput,在状态类中通过 TextEditingController 控制器影响输入值:

在监听中,如果当前激活的关联正则非空,为 _ctrl 的文字内容赋值即可。然后触发 onRegexChange 通知上层:正则表达式内容已发生变化。这在本质上就相当于把点击激活关联正则,看做一种特殊的输入事件:

void _listenLinkRegexChange(BuildContext context, LinkRegexState state) { if (state is LoadedLinkRegexState) { LinkRegex? regex = state.activeRegex; if (regex != null) { if (regex.id == -1) { _ctrl.text = ''; } else { _ctrl.text = regex.regex; } } widget.onRegexChange(_ctrl.text); } }

最后,在切换激活记录时,需要更新 关联正则面板 中的数据。而 切换激活记录 的事件我们在之前已经在监听了。只需要在主页面的 _listenRecordState 中触发 loadLinkRegex 即可。

另外想说一点:BlocListener 也支持通过 listenWhen 回调,对比前后状态来控制监听的条件。比如这里可以根据前后激活 id 是否相等,来判断是否只是激活状态发生了改变。

---->[home_page.dart]---- void _listenRecordState(BuildContext context, RecordState state) { LinkRegexBloc linkRegexBloc = context.read(); MatchBloc matchBloc = context.read(); if (state is LoadedRecordState) { linkRegexBloc.loadLinkRegex(recordId: state.activeRecordId); String content = state.activeRecord.content; matchBloc.add(ChangeContent(content: content)); } if (state is EmptyRecordState) { linkRegexBloc.loadLinkRegex(recordId: -1); matchBloc.add(const ChangeContent(content: "")); } } 5. 保存正则功能

最后,可以在输入框的右侧添加一个保存按钮。点击时可以将输入的正则表达式添加到关联正则中:

通过状态管理,我们很容易通过 MatchBloc 获得当前正则表达式,通过 RecordBloc 获取激活的记录。这样,在任何位置触发添加关联正则的时间都可以,这就是跨界点间数据、事件传递的优势:

void _onSaveLinkRegex() async { String regex = context.read().state.pattern; Record? record = context.read().state.activeRecord; LinkRegexBloc linkRegexBloc = context.read(); if (record != null) { await linkRegexBloc.repository.insert(LinkRegex.i( recordId: record.id, regex: regex, )); linkRegexBloc.loadLinkRegex(recordId: record.id); } }

到这里,就完成了关联正则的功能,应用层的核心需求也就告一段落。下面,我们将从 整个项目 的角度去审视代码结构,并对当前的界面进行优化,以及实现 暗黑模式。

另外,大家可以自己继续在当前应用上进行拓展,在其他面板中添写自己喜欢的东西。比如下面添加 速查面板 ,快速想起正则语法:



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有