Flutter Firestore UI构建插件firestore_ui的使用

Flutter Firestore UI构建插件firestore_ui的使用

firestore_ui

pub package

这个项目最初作为一个PR提交到官方的cloud_firestore插件,但由于他们仍在完善主要功能,因此不得不推迟。该项目基于firebase_database版本。

但是不用担心,我的各位Cloud Firestore用户们,这是一个从那个PR中提取出的主要代码包,现在可以供你们使用!

自定义参数

  • bool linear - 这将使它仅使用.add而不是.insert,这通常在列表更新时(如聊天)导致更少的顺序问题。
  • void onLoaded(QuerySnapshot snapshot) - 您可以使用此方法访问来自流的最新QuerySnapshot
  • bool filter(DocumentSnapshot snapshot) - 这允许你在渲染阶段过滤特定的快照,如果返回true则该项将被过滤。
  • bool debug - 设置此选项可以看到与插入/删除/更新项目相关的所有日志。

如何使用

以下所有示例均来自实际的example文件夹,请运行它们以查看其行为!

列表

将其设置为像ListView.builder一样:

FirestoreAnimatedList(
    query: query,
    itemBuilder: (
        BuildContext context,
        DocumentSnapshot snapshot,
        Animation<double> animation,
        int index,
    ) => FadeTransition(
        opacity: animation,
        child: MessageListTile(
            index: index,
            document: snapshot,
            onTap: _removeMessage,
        ),
    ),
);
网格

将其设置为像GridView.count一样,并同时设置必要的crossAxisCountSliverGridDelegateWithFixedCrossAxisCount的所有其他参数也可用:

FirestoreAnimatedGrid(
    query: query,
    crossAxisCount: 2,
    mainAxisSpacing: 4.0,
    childAspectRatio: 1.0,
    crossAxisSpacing: 4.0,
    itemBuilder: (
        BuildContext context,
        DocumentSnapshot snapshot,
        Animation<double> animation,
        int index,
    ) => FadeTransition(
        opacity: animation,
        child: MessageGridTile(
            index: index,
            document: snapshot,
            onTap: _removeMessage,
        ),
    ),
);
交错网格

将其设置为像StaggeredGridView.countBuilder一样,并同时设置必要的crossAxisCountstaggeredTileBuilder

FirestoreAnimatedStaggered(
    query: query,
    staggeredTileBuilder: (int index, DocumentSnapshot snapshot) => StaggeredTile.count(2, index.isEven ? 2 : 1),
    crossAxisCount: 4,
    mainAxisSpacing: 4.0,
    crossAxisSpacing: 4.0,
    itemBuilder: (
        BuildContext context,
        DocumentSnapshot snapshot,
        Animation<double> animation,
        int index,
    ) => FadeTransition(
        opacity: animation,
        child: MessageGridTile(
            index: index,
            document: snapshot,
            onTap: _removeMessage,
        ),
    ),
);

特别感谢@letsarflutter_staggered_grid_view包!没有它,这部分功能就不会可用;请查看库以了解更多信息!


完整示例

import 'package:firebase_core/firebase_core.dart' show Firebase;
import 'package:firestore_ui/firestore_ui.dart';
import 'package:firestore_ui_example/firebase_options.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

final String title = 'firestore_ui example';

typedef OnSnapshot = Function(DocumentSnapshot<Map<String, dynamic>>?);

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(
    MaterialApp(
      title: title,
      home: MyHomePage(firestore: FirebaseFirestore.instance),
    ),
  );
}

class Movie {
  Movie({
    required this.genre,
    required this.likes,
    required this.poster,
    required this.rated,
    required this.runtime,
    required this.title,
    required this.year,
  });

  Movie.fromJson(Map<String, Object?> json)
      : this(
    genre: (json['genre']! as List).cast<String>(),
    likes: json['likes']! as int,
    poster: json['poster']! as String,
    rated: json['rated']! as String,
    runtime: json['runtime']! as String,
    title: json['title']! as String,
    year: json['year']! as int,
  );

  final String poster;
  final int likes;
  final String title;
  final int year;
  final String runtime;
  final String rated;
  final List<String> genre;

  Map<String, Object?> toJson() {
    return {
      'genre': genre,
      'likes': likes,
      'poster': poster,
      'rated': rated,
      'runtime': runtime,
      'title': title,
      'year': year,
    };
  }
}

class MovieListTile extends StatelessWidget {
  final int index;
  final DocumentSnapshot<Movie>? document;

  const MovieListTile({
    Key? key,
    required this.index,
    required this.document,
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    String title = 'No movie retrieved!';
    if (document != null && document!.exists) {
      final receivedMessage = document!.data()?.title;
      if (receivedMessage != null) title = receivedMessage;
    }

    return ListTile(
      title: Text(title),
      subtitle: Text('Item ${this.index + 1}'),
    );
  }
}

class MessageGridTile extends StatelessWidget {
  final int index;
  final DocumentSnapshot<Movie>? document;

  const MessageGridTile({
    Key? key,
    required this.index,
    required this.document,
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Center(
        child: CircleAvatar(
          backgroundColor: Colors.white,
          child: Text('${this.index + 1}'),
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final FirebaseFirestore firestore;

  MyHomePage({Key? key, required this.firestore}) : super(key: key);

  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final PageController _controller = PageController(initialPage: 0, keepPage: true);

  int _currentIndex = 0;

  void _updateIndex(int value) {
    if (mounted) {
      setState(() => _currentIndex = value);
      _controller.jumpToPage(_currentIndex);
    }
  }

  Query<Movie> get query => widget.firestore.collection('firestore-example-app').withConverter<Movie>(
    fromFirestore: (snapshots, _) => Movie.fromJson(snapshots.data()!),
    toFirestore: (movie, _) => movie.toJson(),
  ).limit(20);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _updateIndex,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.filter_1),
            label: "List",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.filter_2),
            label: "Grid",
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.filter_3),
            label: "Staggered",
          ),
        ],
      ),
      appBar: AppBar(
        title: Text(title),
      ),
      body: PageView(
        controller: _controller,
        children: [
          FirestoreAnimatedList<Movie>(
            debug: false,
            key: ValueKey("list"),
            query: query,
            onLoaded: (snapshot) => print("Received on list: ${snapshot.docs.length}"),
            itemBuilder: (
              BuildContext context,
              snapshot,
              Animation<double> animation,
              int index,
            ) =>
                FadeTransition(
              opacity: animation,
              child: MovieListTile(
                index: index,
                document: snapshot,
              ),
            ),
          ),
          FirestoreAnimatedGrid<Movie>(
            key: ValueKey("grid"),
            query: query,
            onLoaded: (snapshot) => print("Received on grid: ${snapshot.docs.length}"),
            crossAxisCount: 2,
            itemBuilder: (
              BuildContext context,
              snapshot,
              Animation<double> animation,
              int index,
            ) {
              return FadeTransition(
                opacity: animation,
                child: MessageGridTile(
                  index: index,
                  document: snapshot,
                ),
              );
            },
          ),
          FirestoreAnimatedStaggered<Movie>(
            key: ValueKey("staggered"),
            onLoaded: (snapshot) => print("Received on staggered: ${snapshot.docs.length}"),
            staggeredTileBuilder: (int index, DocumentSnapshot? snapshot) => StaggeredTile.count(2, index.isEven ? 2 : 1),
            crossAxisCount: 4,
            query: query,
            itemBuilder: (
              BuildContext context,
              snapshot,
              Animation<double> animation,
              int index,
            ) {
              return FadeTransition(
                opacity: animation,
                child: MessageGridTile(
                  index: index,
                  document: snapshot,
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

更多关于Flutter Firestore UI构建插件firestore_ui的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

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


当然,firestore_ui 是一个 Flutter 插件,它简化了从 Firestore 数据库读取数据并显示在 UI 上的过程。这个插件利用了 Flutter 的 StreamBuilder 和 Firestore 的实时数据特性,能够让你快速构建响应式 UI。

以下是一个简单的示例,展示了如何使用 firestore_ui 插件来从一个 Firestore 集合中读取数据并显示在 ListView 中。

首先,确保你已经在 pubspec.yaml 文件中添加了 firestore_ui 依赖:

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^x.y.z  # 请替换为最新版本号
  firestore_ui: ^x.y.z     # 请替换为最新版本号

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

接下来,创建一个 Flutter 应用并配置 Firestore。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_ui/firestore_ui.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firestore UI Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final CollectionReference<Map<String, dynamic>> usersCollection =
      FirebaseFirestore.instance.collection('users');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firestore UI Demo'),
      ),
      body: FirestoreQueryBuilder<Map<String, dynamic>>(
        query: usersCollection.orderBy('name'), // 假设每个用户文档都有一个 'name' 字段
        builder: (context, snapshot, error) {
          if (error != null) {
            return Text('Error: ${error.message}');
          }

          if (snapshot == null || snapshot.docs.isEmpty) {
            return Center(child: Text('No data'));
          }

          return ListView.builder(
            itemCount: snapshot.docs.length,
            itemBuilder: (context, index) {
              final doc = snapshot.docs[index];
              return ListTile(
                title: Text(doc.data()['name']),
                subtitle: Text(doc.data()['email']), // 假设每个用户文档都有一个 'email' 字段
              );
            },
          );
        },
      ),
    );
  }
}

在这个示例中:

  1. 依赖配置:我们在 pubspec.yaml 中添加了 cloud_firestorefirestore_ui 依赖。
  2. Firestore 集合引用:在 MyHomePage 类中,我们创建了一个对 Firestore 集合 users 的引用。
  3. FirestoreQueryBuilder:我们使用 FirestoreQueryBuilder 小部件来查询 Firestore 数据。这个查询会按照用户的 name 字段进行排序。
  4. UI 构建:根据查询结果,我们构建了一个 ListView 来显示每个用户的 nameemail

确保你已经配置了 Firebase 项目,并在你的 Flutter 应用中正确初始化了 Firestore。这通常涉及到在 main.dart 或其他合适的位置添加 Firebase 初始化代码。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

这个示例展示了 firestore_ui 插件的基本用法,帮助你快速构建响应式 Firestore 数据驱动的 UI。根据你的具体需求,你可以进一步自定义和扩展这个示例。

回到顶部