Flutter 实时数据库 onValue 解析

Posted

技术标签:

【中文标题】Flutter 实时数据库 onValue 解析【英文标题】:Flutter realtime database onValue parsing 【发布时间】:2021-06-09 13:56:04 【问题描述】:

我是第一次尝试使用 NoSQL(firebase 实时数据库),但在 Flutter 应用程序中构建和解析数据时遇到了一些问题。 起初我有一个带有一些属性的简单“人”模型,一切都很好,但后来我不得不引入一个 ID 作为节点并嵌套其他属性以执行 CRUD 操作,现在我无法解析我的“更新“不再是‘人’模型了。 我不知道这是否可以,但为简单起见(我知道这不是正确的 ID),我决定我的 ID('personName' 属性)是人名,因此 DB 上的当前结构是:

我正在使用 freezed 包,PersonDto 看起来像这样(省略域方法):

@freezed
class PersonDto with _$PersonDto  
  const PersonDto ._();

  const factory PersonDto (
    required String personName,
    required int age,
    required String genre,
    required double height,
    required String hobby,
    required double weight,
  ) = _PersonDto ;

  factory PersonDto.fromJson(Map<String, dynamic> json) =>
      _$PersonDto FromJson(json);

在存储库中有一个方法负责从 firebase 接收、解析和流式传输数据。 我的问题基本上是我无法使用节点的键作为“名称”的属性来生成“人”模型。 这是我得到的最接近的:

Stream<Either<PersonFailure, List<Person>>> watchAll() async* 
yield* _firebaseDatabase
    .reference()
    .child('persons')
    .onValue
    .map((event) 
  return right<PersonFailure, List<Person>>(
      (event.snapshot.value as LinkedHashMap).values.map((personMap) 
    final json = Map<String, dynamic>.from(personMap as LinkedHashMap);
//
//this snippet works. I'm able to generate a proper 'Person' model but like this 
//there's no way to retrieve the key from event.snapshot.value
//
    json.addAll(
      'personName': 'NAME OF THE PERSON',
    );
   return PersonDto.fromJson(json).toDomain();     
  ).toList());
).onErrorReturnWith((e) 
  print('WATCH ERROR $e.toString()');
  return left(const PersonFailure.unexpected());
);

应该是这样的,不幸的是,这同样不起作用:

yield* _firebaseDatabase
    .reference()
    .child('persons')
    .onValue
    .map((event) 
  (event.snapshot as LinkedHashMap).map((key, value) 
    final personName = key;
    final json = value as Map<String,dynamic>;
    json.addAll('personName':personName);
    //
    //error: The return type 'Person' isn't a 'MapEntry<_, _>', as required by the closure's context.
    //
    return PersonDto.fromJson(json).toDomain();
  );
);

【问题讨论】:

【参考方案1】:

好的,我设法让它工作。这有点难看,但我找不到更漂亮的方法,所以我们开始吧。

首先,“PersonDto”与 firebase 1:1 映射,并且 freezed 将处理 fromJson/toJson 方法。为了将 DTO 转换为域模型,只是缺少“personName”属性,因此一旦从映射键中提取,我将手动将其提供给“toDomain”方法:

@freezed
class PersonDto with _$PersonDto  
  const PersonDto ._();

  const factory PersonDto (
    required int age,
    required String genre,
    required double height,
    required String hobby,
    required double weight,
  ) = _PersonDto ;

Person toDomain(String personName)
    return Person(
      personName:personName,
      age:age,
      genre:genre,
      height:height,
      hobby:hooby,
      weight:weight,
    )
  

现在是令人讨厌的部分。在存储库方法中,我必须执行大量转换才能生成 Person 对象列表。 关键部分已将从 firebase 收到的 LinkedHashMap 转换为 Map ,然后生成带有条目的 List> 。通过这种方式,我能够使用列表中的 .map 方法,迭代,从键中推断出 personName,从值中生成 PersonDto,最后使用 PersonDto.toDomain 方法返回一个 Person 对象:

Stream<Either<PersonFailure, List<Person>>> watchAll() async* 
yield* _firebaseDatabase
    .reference()
    .child('persons')
    .onValue
    .map((event)    
final firebaseMap = Map<String, dynamic>.from(event.snapshot.value as LinkedHashMap);
final firebaseMapList = List<MapEntry<String, dynamic>>.from(firebaseMap.entries);
final personsList = firebaseMapList.map((personMap) 
    final personName = personMap.key;
    final json = personMap.value as LinkedHashMap<dynamic, dynamic>;
    final personDto =
        PersonDto.fromJson(Map<String, dynamic>.from(json));
    return personDto.toDomain(personName);
  ).toList();
return right<PersonFailure, List<Person>>(personsList );
).onErrorReturnWith((e) =>
 left(const PersonFailure.unexpected()),
);

我现在可以“轻松”按爱好(或按流派或其他)将 firebase 上的人员记录分开,使用 .reference().child('persons').child('hobby') 进行查询,删除从 PersonDto 获取 hobby 参数,然后使用上面的 sn-p 并手动将 hobby 参数提供给 toDomain 方法。

如果有人可以提供更简洁的版本,我会很高兴看看它!

【讨论】:

【参考方案2】:

只需要添加一个空检查。像这样;

.....
.onValue
.map((event) 
    List<ListsModel> _offers = <ListsModel>[];
    if(event.snapshot.value != null) 
        final _resultList =
          Map<String, dynamic>.from(e.snapshot.value as LinkedHashMap);
        for (var key in _resultList.keys) 
        Map<dynamic, dynamic> map = Map.from(_resultList[key]);
        ListsModel listsModel = ListsModel.fromMap(map);
        _offers.add(listModel);
    

.....

【讨论】:

以上是关于Flutter 实时数据库 onValue 解析的主要内容,如果未能解决你的问题,请参考以下文章

在setState()之后更新Flutter页面

获取与用户 ID 匹配的用户帖子:Flutter

基于 Apache Flink + Hologres 的实时推荐系统架构解析

MySQL 到 ClickHouse 实时数据同步实操分享

MySQL 到 ClickHouse 实时数据同步实操分享

MySQL 到 SQL Server 实时数据同步实操分享