Firebase 更新时 Flutter 的错误行为

Posted

技术标签:

【中文标题】Firebase 更新时 Flutter 的错误行为【英文标题】:Wrong behavior of Flutter at Firebase update 【发布时间】:2018-12-04 09:49:19 【问题描述】:

我有下面的代码,它读取baby 的名称和来自firestore 的投票,并使用 thump_up 和 thump_down 按钮表示 Cards 中的信息。

数据在firebase数据库更改时更新,但更新错误,例如在下面的屏幕截图中,我首先输入Karam婴儿数据,并且正确更新,然后我输入'Dana'婴儿数据,但是它用Karam婴儿数据创建了另一张卡片(即在它之前创建的同一张卡片),但是当输入Yara婴儿数据时,已经为这里创建了正确的卡片!我想是因为它在列表的末尾!

而且,在最后的截图中,我从数据库中删除了Danababy的记录,但删除的是最后一张创建的卡片,即Yarababy!

同样的,一旦我们点击thumb_up图标,投票就会在数据库中更新,但不会反映在卡片中:(

main.dart:

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'BabyModel.dart';
import 'BabyCard.dart';

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

class MyApp extends StatefulWidget 
  MyApp();

  @override
  State<StatefulWidget> createState() => MyAppState();


class MyAppState extends State<MyApp> 
  @override
  Widget build(BuildContext ctxt) 
    return StreamBuilder(
      stream: Firestore.instance.collection('baby').snapshots(),
      builder: (_, AsyncSnapshot<QuerySnapshot> snapshot) 
        var documents = snapshot.data?.documents ?? [];
        var baby =
        documents.map((snapshot) => BabyData.from(snapshot)).toList();
        return BabyPage(baby);
      ,
    );
  


class BabyPage extends StatefulWidget 
  final List<BabyData> allBaby;

  BabyPage(this.allBaby);

  @override
  State<StatefulWidget> createState() 
    return BabyPageState();
  



class BabyPageState extends State<BabyPage> 
  @override
  Widget build(BuildContext context) 

  //  var filteredBaby = widget.allFish.where((BabyData data) 
  //    data.name = 'Dana';
  //  ).toList();

    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: SafeArea(
        child: Scaffold(
        body: Container(
        child: ListView.builder(
            itemCount: widget.allBaby.length,
            padding: const EdgeInsets.only(top: 10.0),
            itemBuilder: (context, index) 
              return BabyCard(widget.allBaby[index]);
            )
      ),
    )));
  

BabyModel.dart:

import 'package:cloud_firestore/cloud_firestore.dart';

class BabyData 
  final DocumentReference reference;
  String name;
  int vote;

  BabyData.data(this.reference,
      [this.name,
        this.vote]) 
    // Set these rather than using the default value because Firebase returns
    // null if the value is not specified.
    this.name ??= 'Frank';
    this.vote ??= 7;
  

  factory BabyData.from(DocumentSnapshot document) => BabyData.data(
      document.reference,
      document.data['name'],
      document.data['vote']);

  void save() 
    reference.setData(toMap());
  

  Map<String, dynamic> toMap() 
    return 
      'name': name,
      'vote': vote,
    ;
  

BabyCard.dart:

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

class BabyCard extends StatefulWidget 
  final BabyData baby;

  BabyCard(this.baby);

  @override
  State<StatefulWidget> createState() 
    return BabyCardState(baby);
  


class BabyCardState extends State<BabyCard> 
  BabyData baby;
  String renderUrl;

  BabyCardState(this.baby);

  Widget get babyCard 
    return
      new Card(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            ListTile(
              leading: const Icon(Icons.album),
              title: Text('The $baby.name is having:'),
              subtitle: Text('$baby.vote Votes.'),
            ),
            new ButtonTheme.bar( // make buttons use the appropriate styles for cards
              child: new ButtonBar(
                children: <Widget>[
                  new FlatButton(
                    child: const Icon(Icons.thumb_up),
                    onPressed: () => Firestore.instance.runTransaction((transaction) async 
                       DocumentSnapshot freshSnap =
                           await transaction.get(baby.reference);
                       await transaction.update(
                           freshSnap.reference, 'vote': freshSnap['vote'] + 1);
                ),
                  ),
                  new FlatButton(
                    child: const Icon(Icons.thumbs_up_down),
                    onPressed: ()  /* ... */ ,
                  ),
                  new FlatButton(
                    child: const Icon(Icons.thumb_down),
                    onPressed: ()  /* ... */ ,
                  )]))]));
  

  @override
  Widget build(BuildContext context) 
    return new Container(
          child:  babyCard,
        );
  

下面显示错误的数据添加到卡中,Danababy 不存在:

下图显示从卡中删除的错误数据,Yara 宝贝卡已被删除,在删除 Dana 宝贝记录时!:

【问题讨论】:

【参考方案1】:

您可以使用常规 Widget 作为 Stream 构建器的单元格,而不是使用 Stateful Widget 作为单元格。这在某种程度上是解决该错误的方法,但实际上可能无法解决您的问题。

【讨论】:

【参考方案2】:

下面是codelabs所示的解决方案:

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

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

class MyApp extends StatelessWidget   @override  Widget build(BuildContext context)     return MaterialApp(
     title: 'Baby Names',
     home: MyHomePage(),    );   

class MyHomePage extends StatefulWidget   @override  _MyHomePageState createState()     return _MyHomePageState();   

class _MyHomePageState extends State<MyHomePage>   @override  Widget build(BuildContext context)     return Scaffold(
     appBar: AppBar(title: Text('Baby Name Votes')),
     body: _buildBody(context),    );  

 Widget _buildBody(BuildContext context)     return StreamBuilder<QuerySnapshot>(
     stream: Firestore.instance.collection('baby').snapshots(),
     builder: (context, snapshot) 
       if (!snapshot.hasData) return LinearProgressIndicator();

       return _buildList(context, snapshot.data.documents);
     ,    );  

 Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot)     return ListView(
     padding: const EdgeInsets.only(top: 20.0),
     children: snapshot.map((data) => _buildListItem(context, data)).toList(),    );  

 Widget _buildListItem(BuildContext context, DocumentSnapshot data)   final record = Record.fromSnapshot(data);

   return Padding(
     key: ValueKey(record.name),
     padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
     child: Container(
       decoration: BoxDecoration(
         border: Border.all(color: Colors.grey),
         borderRadius: BorderRadius.circular(5.0),
       ),
       child: ListTile(
         title: Text(record.name),
         trailing: Text(record.votes.toString()),
         onTap: () => Firestore.instance.runTransaction((transaction) async 
               final freshSnapshot = await transaction.get(record.reference);
               final fresh = Record.fromSnapshot(freshSnapshot);

               await transaction
                   .update(record.reference, 'votes': fresh.votes + 1);
             ),
       ),
     ),    );   

class Record   final String name;  final int votes;  final DocumentReference reference;

 Record.fromMap(Map<String, dynamic> map, this.reference)
     : assert(map['name'] != null),
       assert(map['votes'] != null),
       name = map['name'],
       votes = map['votes'];

 Record.fromSnapshot(DocumentSnapshot snapshot)
     : this.fromMap(snapshot.data, reference: snapshot.reference);

 @override  String toString() => "Record<$name:$votes>"; 

【讨论】:

【参考方案3】:

我的代码的确切替换是:

main.dart:

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

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

class MyApp extends StatelessWidget 
  @override Widget build(BuildContext context) 
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  


class MyHomePage extends StatefulWidget 
  @override _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  @override Widget build(BuildContext context) => BabyPage();


class BabyPage extends StatefulWidget 
  @override State<StatefulWidget> createState() => BabyCardState();

BabyCard.dart:

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

class BabyCardState extends State 

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: Text('Baby Name Votes')),
      body: _buildBody(context),
    );
  

  Widget _buildBody(BuildContext context) 
    return StreamBuilder<QuerySnapshot>(
      stream: Firestore.instance.collection('baby').snapshots(),
      builder: (context, snapshot) 
        if (!snapshot.hasData) return LinearProgressIndicator();

        return _buildList(context, snapshot.data.documents);
      ,
    );
  

  Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) 
    return ListView(
      padding: const EdgeInsets.only(top: 20.0),
      children: snapshot.map((data) => _buildListItem(context, data)).toList(),
    );
  

  Widget _buildListItem(BuildContext context, DocumentSnapshot data) 
    final baby = BabyData.fromSnapshot(data);

    return Padding(
      key: ValueKey(baby.name),
      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(5.0),
        ),

        child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ListTile(
                  leading: const Icon(Icons.album),
                  title: Text('The $baby.name is having:'),
                  subtitle: Text('$baby.votes Votes.'),
                  trailing: Text(baby.votes.toString())
              ),

              new ButtonTheme.bar(
                  child: new ButtonBar(

                      children: <Widget>[
                        FlatButton(
                          child: const Icon(Icons.thumb_up),
                          onPressed: () =>
                              Firestore.instance.runTransaction((
                                  transaction) async 
                                final freshSnapshot = await transaction.get(
                                    baby.reference);
                                final fresh = BabyData.fromSnapshot(
                                    freshSnapshot);

                                await transaction
                                    .update(
                                    baby.reference, 'votes': fresh.votes + 1);
                              ),
                        ),
                        FlatButton(
                          child: const Icon(Icons.thumbs_up_down),
                          onPressed: () 
                            print(baby);
                          ,
                        ),
                        FlatButton(
                          child: const Icon(Icons.thumb_down),
                          onPressed: () 
                            Firestore.instance.runTransaction((
                                transaction) async 
                              final freshSnapshot = await transaction.get(
                                  baby.reference);
                              final fresh = BabyData.fromSnapshot(
                                  freshSnapshot);

                              await transaction
                                  .update(
                                  baby.reference, 'votes': fresh.votes - 1);
                            );
                          ,
                        )
                      ])
              )
            ]),
      ),
    );
  

BabyModel.dart:

import 'package:cloud_firestore/cloud_firestore.dart';

class BabyData 
  final String name;
  final int votes;
  final DocumentReference reference;

  BabyData.fromMap(Map<String, dynamic> map, this.reference) :
          name = (map['name'] ?? 'Frank'),
          votes = (map['votes'] ?? 7);

  BabyData.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  // if want to use: onTap: () => print(baby),
  @override String toString() => "Record<$name:$votes>";

android/app/src/gradle.build 中添加multiDexEnabled 为:

android 
  defaultConfig 
    multiDexEnabled true
    

注意

对于 Android,请确保出现相同的包名称: - 体现 - 构建.gradle - .kt/.java 文件

【讨论】:

你能解释一下你实际做了什么来解决这个问题吗?

以上是关于Firebase 更新时 Flutter 的错误行为的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Firebase Android/ios Google 登录错误

Firebase 核心依赖错误(Flutter/Firebase)

Firebase Query 不适用于 Flutter,如何解决?

使用 Firebase 托管部署我的 Flutter Web 应用程序后出现错误

使用 Provider (Flutter) 包进行异步异常处理 (Firebase)

在android studio中将firebase添加到flutter时出错:插件项目:firebase_core_web未找到。请更新 settings.gradle [重复]