Flutter Firestore辅助插件firestore_helpers的使用

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

Flutter Firestore辅助插件firestore_helpers的使用

DISCLAIMER

由于一些用户指出,Firestore目前在比较GeoPoints时存在不正确的问题,这可能导致从服务器返回的数据比服务器查询预期的更多。只要您始终通过提供距离访问器来使用客户端过滤,您将获得正确的结果,但如果获取大量数据条目,性能会下降。Google希望在未来对此进行更改,但没有具体时间表。请参阅 GitHub上的问题


Warning

V3.0.0有一个重大变化,因为它修复了一个拼写错误 clientSite > < clientSide


FirestoreHelpers

Firestore是一个易于使用的数据库。为了使生活更加轻松,这里有一个包。它包含用于动态构建查询和位置查询的功能。

这些地理计算相关的数学是从 Stack Overflow上的一段JS源代码Stanton Parham 编写的。


创建动态查询

如果您想在运行时修改查询,请考虑使用 builtQuery()

/// 
/// Builds a query dynamically based on a list of [QueryConstraint] and orders the result based on a list of [OrderConstraint].
/// [collection] : the source collection for the new query
/// [constraints] : a list of constraints that should be applied to the [collection]. 
/// [orderBy] : a list of order constraints that should be applied to the [collection] after the filtering by [constraints] was done.
/// Important all limitation of FireStore apply for this method two on how you can query fields in collections and order them.
Query buildQuery({Query collection, List<QueryConstraint> constraints,
    List<OrderConstraint> orderBy})

/// Used by [buildQuery] to define a list of constraints. Important besides the [field] property not more than one of the others can ne [!=null].
/// They corespond to the possisble parameters of Firestore`s [where()] method. 
class QueryConstraint {
  QueryConstraint(
      {this.field,
      this.isEqualTo,
      this.isLessThan,
      this.isLessThanOrEqualTo,
      this.isGreaterThan,
      this.isGreaterThanOrEqualTo,
      this.isNull,
      this.arrayContains});
}

/// Used by [buildQuery] to define how the results should be ordered. The fields 
/// corespond to the poss isible parameters of Firestore`s [oderby()] method. 
class OrderConstraint {
  OrderConstraint(this.field, this.descending);
}

示例

假设我们有一个名为 events 的的事件集合,并且我们想要获取满足某些约束的所有事件:

// 假设我们的 Event 类看起来像这样

class Event{
  String id;
  String name;
  DateTime startTime;
  GeoPoint location;
}

Stream<List<Event>> getEvents({List<QueryConstraint> constraints}) {
  try {
    Query ref = buildQuery(
      collection: eventCollection, 
      constraints: constraints, orderBy: [
          new OrderConstraint("startTime", false),
        ]);
    return ref.snapshots().map((snapShot) =&gt; snapShot.documents.map(eventDoc) {
          var event = _eventSerializer.fromMap(eventDoc.data);
          event.id = eventDoc.documentID;
          return event;
        }).toList());
  } on Exception catch (ex) {
    print(ex);
  }
  return null;
}

/// 并在其他地方调用

getEvents(constraints: [new QueryConstraint(field: "creatorId", isEqualTo: _currentUser.id)]);

为了使其更舒适和 更强大,有 getDataFromQuery() 方法:

typedef DocumentMapper&lt;T&gt; = T Function(DocumentSnapshot document);
typedef ItemFilter&lt;T&gt; = bool Function(T);
typedef ItemComparer&lt;T&gt; = int Function(T item1, T item2);

///
/// Convenience Method to access the data of a Query as a stream while applying 
/// a mapping function on each document with optional client side filtering and sorting
/// [qery] : the data source
/// [mapper] : mapping function that gets applied to every document in the query. 
/// Typically used to deserialize the Map returned from FireStore
/// [clientSideFilters] : optional list of filter functions that execute a `.where()` 
/// on the result on the client side
/// [orderComparer] : optional comparisson function. If provided your resulting data 
/// will be sorted based on it on the client side

Stream&lt;List&lt;T&gt;&gt; getDataFromQuery&lt;T&gt;({
  Query query,
  DocumentMapper&lt;T&gt; mapper,
  List&lt;ItemFilter&gt; clientSidefilters,
  ItemComparer&lt;T&gt; orderComparer,
});

在这个例子中,我们的事件具有以下功能:

我们需要按名称对 未来事件排序。 在这种情况下,我们在服务器端不做排序,而是在客户端侧后执行排序。

Stream&lt;List&lt;Event&gt;&gt; getEvents({List&lt;QueryConstraint&gt; constraints}) {
  try {
    Query query = buildQuery(collection: eventCollection, constraints: constraints);
    return getDataFromQuery(
        query: query, 
        mapper: (eventDoc) {
          var event = _eventSerializer.fromMap(eventDoc.data);
          event.id = eventDoc.documentID;
          return event;
        }, 
        clientSidefilters: (event) =&gt; event.startTime &gt; DateTime.now()  // 只有未来的事件
        orderComparer: (event1, event2) =&gt; event.name.compareTo(event2.name) 
      );
  } on Exception catch (ex) {
    print(ex);
  }
  return null;
}

地理位置查询

不幸的是,FireStore 不支持真正的地理查询,但我们可以通过比较 Geopoints 来查询小于和 大于。 这允许定义搜索区域的南西和 北东角。

大多数应用程序需要定义一个搜索区域为中心点 和 半径。 我们有 calculateBoundingBoxCoordinates 方法。

/// Defines the boundingbox for the query based
/// on its south-west and north-east corners
class GeoBoundingBox {
  final GeoPoint swCorner;
  final GeoPoint neCorner;

  GeoBoundingBox({this.swCorner, this.neCorner});
}

///
/// Defines the search area by a circle [center] / [radiusInKilometers]
/// Based on the limitations of FireStore we can only search in rectangles
/// which means that from this definition a final search square is calculated
/// that contains the circle
class Area {
  final GeoPoint center;
  final double radiusInKilometers;

  Area(this.center, this.radiusInKilometers): 
  assert(geoPointValid(center)), assert(radiusInKilometers &gt;= 0);

  factory Area.inMeters(GeoPoint gp, int radiusInMeters) {
    return new Area(gp, radiusInMeters / 1000.0);
  }

  factory Area.inMiles(GeoPoint gp, int radiusMiles) {
    return new Area(gp, radiusMiles * 1.60934);
  }

  /// returns the distance in km of [point] to center
  double distanceToCenter(GeoPoint point) {
    return distanceInKilometers(center, point);
  }
}

///
///Calculates the SW and NE corners of a bounding box around a center point for a given radius;
/// [area] with the center given as .latitude and .longitude
/// and the radius of the box (in kilometers)
GeoBoundingBox boundingBoxCoordinates(Area area)

如果使用 buildQuery() 则可以更简单地使用 getLocationsConstraint 方法:

/// Creates the necessary constraints to query for items in a FireStore collection that are inside a specific range from a center point
/// [fieldName] : the name of the field in FireStore where the location of the items is stored
/// [area] : Area within that the returned items should be
List&lt;QueryConstraint&gt; getLocationsConstraint(String fieldName, Area area) 
/// function type used to acces the field that contains the loaction inside 
/// the generic type
typedef LocationAccessor&lt;T&gt; = GeoPoint Function(Templele item);

/// function typse used to access the distance field that contains the 
/// distance to the target inside the generic type
typedef DistanceAccessor&lt;T&gt; = double Function(Tempele item);

typedef DistanceMapper&lt;T&gt; = Tempele Function(Tempele item, double itemsDistance);

getDataInArea() 结合了上述所有函数成为一个非常强大的函数:


更多关于Flutter Firestore辅助插件firestore_helpers的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Firestore辅助插件firestore_helpers的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用firestore_helpers插件来简化与Firestore交互的示例。firestore_helpers是一个辅助库,它提供了一些方便的函数和扩展来处理常见的Firestore操作。

首先,确保你的pubspec.yaml文件中包含了firestore_helpers依赖:

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^3.1.0  # 确保版本与你的项目兼容
  firestore_helpers: ^2.0.0  # 确保版本与你的项目兼容

然后运行flutter pub get来安装依赖。

接下来,我们来看一个使用firestore_helpers的示例。假设我们有一个User模型,并且我们希望从Firestore中读取和写入这些数据。

定义User模型

import 'package:cloud_firestore/cloud_firestore.dart';

class User {
  String id;
  String name;
  int age;

  User({required this.id, required this.name, required this.age});

  // 从DocumentSnapshot转换
  factory User.fromSnap(DocumentSnapshot snap) {
    Map<String, dynamic> data = snap.data()! as Map<String, dynamic>;
    return User(
      id: snap.id,
      name: data['name'] as String,
      age: data['age'] as int,
    );
  }

  // 转换为Map用于Firestore存储
  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'age': age,
    };
  }
}

使用firestore_helpers进行读写操作

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_helpers/firestore_helpers.dart';
import 'user_model.dart'; // 假设User类定义在这个文件中

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirestoreExample(),
    );
  }
}

class FirestoreExample extends StatefulWidget {
  @override
  _FirestoreExampleState createState() => _FirestoreExampleState();
}

class _FirestoreExampleState extends State<FirestoreExample> {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  CollectionReference<Map<String, dynamic>> usersCollection =
      _firestore.collection('users');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firestore Helpers Example'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ElevatedButton(
            onPressed: () async {
              // 写入数据
              User user = User(id: '1', name: 'Alice', age: 30);
              await usersCollection.doc(user.id).set(user.toMap());
            },
            child: Text('Add User'),
          ),
          ElevatedButton(
            onPressed: () async {
              // 读取数据
              QuerySnapshot<Map<String, dynamic>> snapshot = await usersCollection.get();
              List<User> users = snapshot.docs.map((doc) => User.fromSnap(doc)).toList();
              print('Users: $users');
            },
            child: Text('Get Users'),
          ),
          ElevatedButton(
            onPressed: () async {
              // 使用firestore_helpers的批量写入
              Batch batch = _firestore.batch();
              List<User> newUsers = [
                User(id: '2', name: 'Bob', age: 25).toMap(),
                User(id: '3', name: 'Charlie', age: 35).toMap(),
              ].map((user) {
                DocumentReference ref = usersCollection.doc(user['id'] as String);
                batch.set(ref, user);
                return ref;
              }).toList();

              await batch.commit();
              print('Batch write completed');
            },
            child: Text('Batch Add Users'),
          ),
        ],
      ),
    );
  }
}

说明

  1. User模型:我们定义了一个User类,它包含了从Firestore文档转换和映射到Firestore文档的方法。
  2. Firestore操作
    • 添加单个用户:使用set方法将单个用户文档写入Firestore。
    • 获取用户列表:使用get方法从Firestore集合中读取所有用户文档,并将它们转换为User对象列表。
    • 批量添加用户:使用Batch对象来执行批量写入操作,这是firestore_helpers本身不提供的功能,但它是Firestore SDK的一部分,这里展示如何结合使用。

firestore_helpers库本身可能不包含上述所有功能的直接实现,但它通常会提供一些方便的扩展函数或类来简化Firestore操作。上述代码展示了如何使用Flutter和Firestore SDK结合一些常见的模式来处理数据。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!