我正在尝试构建一个简单的报价Flutter应用程序,在其中显示报价列表并允许用户“喜欢”报价。我正在为此使用Streambuilder。我的问题是,即使我最多有50个引号,Firestore的使用情况仪表板仍显示大量读取(每个用户近300个)。我有一种预感,我的代码中的某些内容导致Streambuilder多次触发(也许用户“喜欢”引号),并且Streambuilder正在加载所有引号,而不是仅加载用户视口中的引号。对于如何解决此问题以减少读取次数的任何帮助,将不胜感激。
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:positivoapp/utilities.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:share/share.dart';
class QuotesScreen extends StatefulWidget {
@override
QuotesScreenLayout createState() => QuotesScreenLayout();
}
class QuotesScreenLayout extends State<QuotesScreen> {
List<String> quoteLikeList = new List<String>();
// Get Goals from SharedPrefs
@override
void initState() {
super.initState();
getQuoteLikeList();
}
Future getQuoteLikeList() async {
if (Globals.globalSharedPreferences.getString('quoteLikeList') == null) {
print("No quotes liked yet");
return;
}
String quoteLikeListString =
Globals.globalSharedPreferences.getString('quoteLikeList');
quoteLikeList = List.from(json.decode(quoteLikeListString));
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection(FireStoreCollections.QUOTES)
.orderBy('timestamp', descending: true)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(),
new Text("Loading..."),
],
);
default:
print('Loading Quotes Stream');
return new ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return new QuoteCard(
quote:
Quote.fromMap(document.data, document.documentID),
quoteLikeList: quoteLikeList,
);
}).toList(),
);
}
},
)),
),
);
}
}
class QuoteCard extends StatelessWidget {
Quote quote;
final _random = new Random();
List<String> quoteLikeList;
QuoteCard({@required this.quote, @required this.quoteLikeList});
@override
Widget build(BuildContext context) {
bool isLiked = false;
String likeText = 'LIKE';
IconData icon = Icons.favorite_border;
if (quoteLikeList.contains(quote.quoteid)) {
icon = Icons.favorite;
likeText = 'LIKED';
isLiked = true;
}
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
constraints: new BoxConstraints.expand(
height: 350.0,
width: 400,
),
child: Stack(children: <Widget>[
Container(
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.25), BlendMode.darken),
image: AssetImage('images/${quote.imageName}'),
fit: BoxFit.cover,
),
),
),
Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
quote.quote,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
fontFamily: 'bold',
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 255, 255, 1)),
),
),
),
]),
),
Padding(
padding: EdgeInsets.fromLTRB(18, 10, 10, 0),
child: Text(
'Liked by ${quote.numLikes} happy people',
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: 'bold',
fontWeight: FontWeight.bold,
color: Colors.black),
),
),
ButtonBar(
alignment: MainAxisAlignment.start,
children: <Widget>[
FlatButton(
child: UtilityFunctions.buildButtonRow(Colors.red, icon, likeText),
onPressed: () async {
// User likes / dislikes this quote, do 3 things
// 1. Save like list to local storage
// 2. Update Like number in Firestore
// 3. Toggle isLiked
// 4. Setstate - No need
// Check if the quote went from liked to unlike or vice versa
if (isLiked == false) {
// False -> True, increment, add to list
quoteLikeList.add(quote.quoteid);
Firestore.instance
.collection(FireStoreCollections.QUOTES)
.document(quote.documentID)
.updateData({'likes': FieldValue.increment(1)});
isLiked = true;
} else {
// True -> False, decrement, remove from list
Firestore.instance
.collection(FireStoreCollections.QUOTES)
.document(quote.documentID)
.updateData({'likes': FieldValue.increment(-1)});
quoteLikeList.remove(quote.quoteid);
isLiked = false;
}
// Write to local storage
String quoteLikeListJson = json.encode(quoteLikeList);
print('Size of write: ${quoteLikeListJson.length}');
Globals.globalSharedPreferences.setString(
'quoteLikeList', quoteLikeListJson);
// Guess setState(); will happen via StreamBuilder - Yes
// setState(() {});
},
),
],
),
],
),
),
);
}
}
答案 0 :(得分:3)
您的直觉是正确的。由于Streambuilder在Build方法中,因此每次重新构建窗口小部件树时,都会在Firestore上进行读取。这比我能here解释得更好。
为防止这种情况发生,您应该在initState方法中侦听Firestore流。这样,它将仅被调用一次。像这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
{% block styles %}
{% endblock %}
<link rel="stylesheet" type="stylesheet" href="main_styles.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
{% block content %}
{% endblock %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
</body>
</html>