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
婴儿数据时,已经为这里创建了正确的卡片!我想是因为它在列表的末尾!
而且,在最后的截图中,我从数据库中删除了Dana
baby的记录,但删除的是最后一张创建的卡片,即Yara
baby!
同样的,一旦我们点击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,
);
下面显示错误的数据添加到卡中,Dana
baby 不存在:
下图显示从卡中删除的错误数据,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 [重复]