while retrieving data from firebase in a flutter app - flutter

This is the code
import 'package:flutter/material.dart';
import 'Authentication.dart';
import 'photoUpload.dart';
import 'Posts.dart';
import 'package:firebase_database/firebase_database.dart';
// import 'package:flutter_blogapp/Authentication.dart';
// import 'package:flutter_blogapp/photoUpload.dart';
class HomePage extends StatefulWidget
{
HomePage
(
{
this.auth,
this.onSignedOut,
}
);
final AuthImplementation auth;
final VoidCallback onSignedOut;
#override
State<StatefulWidget> createState()
{
return _HomePageState();
}
}
class _HomePageState extends State<HomePage>
{
List<Posts> postsList = [];
#override
void initState()
{
super.initState();
DatabaseReference postsRef = FirebaseDatabase.instance.reference().child("Posts");
postsRef.once().then((DataSnapshot snap)
{
var KEYS = snap.value.keys;
var DATA = snap.value;
postsList.clear();
for(var individualKey in KEYS)
{
Posts posts = new Posts
(
DATA[individualKey]['image'],
DATA[individualKey]['desctiption'],
DATA[individualKey]['data'],
DATA[individualKey]['time'],
);
postsList.add(posts);
}
setState(()
{
print('Length : $postsList.length');
});
});
}
void _logoutUser() async
{
try
{
await widget.auth.signOut();
widget.onSignedOut();
}
catch (e)
{
print(e.toString());
}
}
#override
Widget build(BuildContext context)
{
return new Scaffold
(
appBar: new AppBar
(
title: new Text('Home'),
),
body : new Container
(
child: postsList.length == 0 ? new Text(" No Post available ") : new ListView.builder
(
itemCount: postsList.length,
itemBuilder: (_, index)
//itemBuilder: (BuildContext _, int index ) //<-----
{
return PostsUI(postsList[index].image, postsList[index].description, postsList[index].date, postsList[index].time);
}
),
),
bottomNavigationBar: new BottomAppBar
(
color: Colors.pink,
child: new Container
(
margin: const EdgeInsets.only(left: 70.0, right: 70.0),
child: new Row
(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>
[
new IconButton
(
icon: new Icon(Icons.local_car_wash),
iconSize: 50,
color: Colors.white,
onPressed: _logoutUser,
),
new IconButton
(
icon: new Icon(Icons.add_a_photo),
iconSize: 50,
color: Colors.white,
onPressed: ()
{
Navigator.push
(
context,
MaterialPageRoute(builder: (context)
{
return new UploadPhotoPage();
})
);
},
),
],
),
),
),
);
}
// Designing Posts UI <remove from Text field><??'defaut value'>
Widget PostsUI(String image, String description, String date, String time)
{
return new Card
(
elevation: 10.0,
margin: EdgeInsets.all(15.0),
child: new Container
(
padding: new EdgeInsets.all(14.0),
child: new Column
(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>
[
new Row
(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>
[
new Text
(
date,
style: Theme.of(context).textTheme.subtitle,
textAlign: TextAlign.center,
),
new Text
(
time,
style: Theme.of(context).textTheme.subtitle,
textAlign: TextAlign.center,
), //<----
],
),
SizedBox(height: 10.0,),
new Image.network(image, fit:BoxFit.cover),
SizedBox(height: 10.0,),
new Text
(
description, //= null ? "true" : "False", //??'defaut value'
style: Theme.of(context).textTheme.subhead,
textAlign: TextAlign.center,
),
],
)
)
);
}
}
The error that i was getting
The following assertion was thrown building: A non-null String must be
provided to a Text widget. 'package:flutter/src/widgets/text.dart':
Failed assertion: line 285 pos 10: 'data != null'
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
When the exception was thrown, this was the stack
2 new Text package:flutter/…/widgets/text.dart:285
3 _HomePageState.PostsUI package:flutter_blogapp/HomePage.dart:184
4 _HomePageState.build. package:flutter_blogapp/HomePage.dart:100
5 SliverChildBuilderDelegate.build package:flutter/…/widgets/sliver.dart:446
6 SliverMultiBoxAdaptorElement._build. package:flutter/…/widgets/sliver.dart:1260
What can i do to retrieve the data in my app? I have tried what i can do. can some one help me in finding where am I wrong? I have started learning recently.

First thing, pull the code out of the initState and put in a different function. This will keep your initState clean.
What I can see is you're trying to update data in firestore.
What you could do is define a new function as follows:
final databaseReference = Firestore.instance;
setPostsData() async {
DocumentSnapshot snapshot = await databaseReference
.collection("NAME_OF_COLLECTION")
.document("NAME_OF_DOCUMENT")
.get();
// use this DocumentSnapshot snapshot to get the current data that is there in the document inside of your collection.
var currentData = snapshot.data;
print(currentData); // to check whats actually there and if its working...
//lets assume newPostsList is the data that you want to put in this referenced document.
//to update the data in firestore:
await databaseReference
.collection("NAME_OF_COLLECTION")
.document("NAME_OF_DOCUMENT")
.updateData({newPostsList});
}
Then you can put setPostsData in the initState.
For this to work, you might want to redesign your database structure, for example: it should be like users --> posts --> List_of_all_posts. If did this way, NAME_OF_COLLECTION will be users and NAME_OF_DOCUMENT will be posts.

Related

What can I do to make my ListView stop incrementing the data every time I open it?

My first Flutter project, which is a tricycle booking system, has just begun. Using the ListView widget, I wanted to display all of the active passengers that are saved in my Firebase Database. However, when I attempted to display it and place it in a List, all functions are working fine at first click. When you click the button to view the ListView a second time, all of the saved data are replicated. The list continues after my third click and grows by three. The image below illustrates what takes place when I repeatedly click on the ListView.
These are the blocks of code that are utilized for this functionality:
CODE for Functionality
retrieveOnlinePassengersInformation(List onlineNearestPassengersList) async
{
dList.clear();
DatabaseReference ref = FirebaseDatabase.instance.ref().child("passengers");
for(int i = 0; i<onlineNearestPassengersList.length; i++)
{
await ref.child(onlineNearestPassengersList[i].passengerId.toString())
.once()
.then((dataSnapshot)
{
var passengerKeyInfo = dataSnapshot.snapshot.value;
dList.add(passengerKeyInfo);
print("passengerKey Info: " + dList.toString());
});
}
}
CODE for the UI
body: ListView.builder(
itemCount: dList.length,
itemBuilder: (BuildContext context, int index)
{
return GestureDetector(
onTap: ()
{
setState(() {
chosenPassengerId = dList[index]["id"].toString();
});
Navigator.pop(context, "passengerChoosed");
},
child: Card(
color: Colors.grey,
elevation: 3,
shadowColor: Colors.green,
margin: EdgeInsets.all(8.0),
child: ListTile(
leading: Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Icon(
Icons.account_circle_outlined,
size: 26.sp,
color: Color(0xFF777777),
),
),
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
dList[index]["first_name"] + " " + dList[index]["last_name"],
style: TextStyle(
fontFamily: "Montserrat",
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Icon(
Icons.verified_rounded,
color: Color(0xFF0CBC8B),
size: 22.sp,
),
],
),
],
),
),
),
);
},
),
Expected Result:
Actual Result AFTER CLICKING MANY TIMES:
Made a demo for you how to call function once on load
class CustomWidgetName extends StatefulWidget {
const CustomWidgetName({Key? key}) : super(key: key);
#override
State<CustomWidgetName> createState() => _CustomWidgetNameState();
}
class _CustomWidgetNameState extends State<CustomWidgetName> {
List? dList = [];
void myDataFunction() async {
// do your data fetch and add to dList
final newList = [];
setState(() {
dList = newList;
});
}
#override
void initState() {
super.initState();
myDataFunction(); // Call your async function here
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Try this solution.
Update SelectNearestActiveDriversScreen() like this:
class SelectNearestActiveDriversScreen extends StatefulWidget
{
DatabaseReference? referenceRideRequest;
final List list;
SelectNearestActiveDriversScreen({this.referenceRideRequest, required this.list});
#override
State<SelectNearestActiveDriversScreen> createState() => _SelectNearestActiveDriversScreenState();
}
In homepage.dart, declare List dList = [];, then change line 378 like this:
Navigator.push(context, MaterialPageRoute(builder: (c)=> SelectNearestActiveDriversScreen(list: dList)));
In SelectNearestActiveDriversScreen(), replace all dList with widget.list.
Finally, if you are using variables in a specific file declare them in that file(not in another file) or pass them in the constructor of the class / file / widget /screen you are calling.
If you would rather use global variables and state managers go for packages like GetX.

Stack with global z-index?

I have a DataTable in which some cells have links. Ideally, I would like to fetch a preview about the link's content whenever hovering over the link, which I was able to achieve using the Stack widget. However, since the stacked preview is inside the DataCell, it seems like I'm not able to raise its "z-index" to be on top of the rest of the table.
Is this not possible with Flutter, or is there a way around it?
The only way I imagine this working, without something to update a global z-index, would be for the cell to update a global state and then have the thumbnail preview appear on a Stack above the DataTable level. But I wish there was a less clunkier way to do it...
3 widgets I've tried but to no avail — they might work, I don't know —:
Tooltip
Overlay
FloatingActionButton
My whole app is here, and the precise commit is 0303732. The relevant code is this ClickableLink widget:
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
import '../schema/links.dart';
#immutable
class ClickableLink extends StatefulWidget {
const ClickableLink({
Key? key,
required this.link,
this.linkText,
this.color = Colors.blue,
}) : super(key: key);
final Link link;
final String? linkText;
final Color color;
#override
State<ClickableLink> createState() => _ClickableLinkState();
}
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
setState(() {
if (widget.link.host == 'online-go.com' && widget.link.prePath == 'game') {
hoverWidget = Positioned(
top: 25,
child: Image.network('https://online-go.com/api/v1/games/${widget.link.id}/png'),
);
}
});
}
void _onExit(PointerEvent pointerEvent) {
setState(() {
hoverWidget = const SizedBox.shrink();
});
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
onExit: _onExit,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(color: widget.color),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
hoverWidget,
],
),
);
}
}
The problem here is due to the fact that your Stack widget, defined inside ClickableLink, will be at a "lower" point (inside your app widget tree) than every other GameResultCell.
So even the higher z-index will still be behind the other GameResultCells.
To fix this I would reccomend changing your structure and define an higher point in your structure to show the preview.
Another way could be using a library to nest your preview inside a tooltip. Take a look at this one for example:
just_the_tooltip: ^0.0.11+2. With this package, you could even use a StatelessWidget.
The result here is more similar to what I suppose you were expecting.
class ClickableLink extends StatelessWidget {
#override
Widget build(BuildContext context) {
return JustTheTooltip(
content: Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png',
),
child: SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
);
}
}
Lastly you could use a Dialog, but the resulting behaviour is a bit different.
Take a look at this code if you want to try:
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png'),
const SizedBox(
height: 16.0,
),
TextButton(
onPressed: () async => launch(widget.link.completeLink),
child: const Text('Go to complete link'))
],
),
);
},
);
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
],
),
);
}
}

Flutter FutureProvider Value Not Updating In Builder Method

The Problem
I am building a basic app in Flutter that gets the user's location and displays nearby places in a swipe-card format similar to Tinder. I managed to implement geolocation however when using FutureProvider/Consumer I'm experiencing a weird bug where the user's relative distance to the place is overwritten with the first distance value in the card deck. Although I am new to flutter and the Provider package, I believe there is a simple fix to this.
Side note: After searching around on Google, I attempted to use FutureProvider.value() to prevent the old value from updating but had no luck.
Thank you in advance for any assistance or direction!
A Quick Demo
Packages Used
card_swipe.dart
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:provider/provider.dart';
import 'package:swipe_stack/swipe_stack.dart';
import '../services/geolocator_service.dart';
import '../models/place.dart';
class CardSwipe extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _currentPosition = Provider.of<Position>(context);
final _placesProvider = Provider.of<Future<List<Place>>>(context);
final _geoService = GeoLocatorService();
return FutureProvider(
create: (context) => _placesProvider,
child: Scaffold(
backgroundColor: Colors.grey[300],
body: (_currentPosition != null)
? Consumer<List<Place>>(
builder: (_, places, __) {
return (places != null)
? Column(
children: [
SizedBox(height: 10.0),
Container(
margin: EdgeInsets.only(top: 120.0),
height: 600,
child: SwipeStack(
children: places.map((place) {
return SwiperItem(builder:
(SwiperPosition position,
double progress) {
return FutureProvider(
create: (context) =>
_geoService.getDistance(
_currentPosition.latitude,
_currentPosition.longitude,
place.geometry.location.lat,
place.geometry.location.lng),
child: Consumer<double>(
builder: (_, distance, __) {
return (distance != null)
? Center(
child: Card(
child: Container(
height: 200,
width: 200,
child: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Text(place.name),
Text(
'${(distance / 1609).toStringAsFixed(3)} mi'), // convert meter to mi
],
),
),
),
),
)
: Container();
}),
);
});
}).toList(),
visibleCount: 3,
stackFrom: StackFrom.Top,
translationInterval: 6,
scaleInterval: 0.03,
onEnd: () => debugPrint("onEnd"),
onSwipe: (int index, SwiperPosition position) =>
debugPrint("onSwipe $index $position"),
onRewind:
(int index, SwiperPosition position) =>
debugPrint("onRewind $index $position"),
),
),
],
)
: Center(
child: CircularProgressIndicator(),
);
},
)
: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
geolocator_service.dart
import 'package:geolocator/geolocator.dart';
class GeoLocatorService {
final geolocator = Geolocator();
Future<Position> getLocation() async {
return await geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
locationPermissionLevel: GeolocationPermission.location,
);
}
Future<double> getDistance(
double startLat, double startLng, double endLat, double endLng) async {
return await geolocator.distanceBetween(startLat, startLng, endLat, endLng);
}
}
place.dart
Quick note: Place class does import a custom class called geometry.dart however this is purely for structuring the Place object and I'm certain it doesn't affect the bug. Therefore, it has been omitted.
import './geometry.dart';
class Place {
final String name;
final Geometry geometry;
Place(this.name, this.geometry);
Place.fromJson(Map<dynamic, dynamic> parsedJson)
: name = parsedJson['name'],
geometry = Geometry.fromJson(
parsedJson['geometry'],
);
}
You have to add a key to the SwiperItem with some unique value (like the name of the place) since currently flutter thinks that the widget has stayed the same so the Consumer gets the state of the old topmost widget.
By adding the key you tell flutter that you removed the topmost widget and the new topmost is in fact the second widget

Question about Flutter State and retrieving variables from State vs StatefulWidget

Here's the context:
In my app, users can create a question, and all questions will be displayed on a certain page. This is done with a ListView.builder whose itemBuilder property returns a QuestionTile.
The problem:
If I create a new question, the text of the new question is (usually) displayed as the text of the previous question.
Here's a picture of me adding three questions in order, "testqn123", "testqn456", "testqn789", but all are displayed as "testqn123".
Hot restarting the app will display the correct texts for each question, but hot reloading wont work.
In my _QuestionTileState class, if I change the line responsible for displaying the text of the question on the page, from
child: Text(text)
to
child: Text(widget.text)
the issue will be resolved for good. I'm not super familiar with how hot restart/reload and state works in flutter, but can someone explain all of this?
Here is the code for QuestionTile and its corresponding State class, and the line changed is the very last line with words in it:
class QuestionTile extends StatefulWidget {
final String text;
final String roomName;
final String roomID;
final String questionID; //
QuestionTile({this.questionID, this.text, this.roomName, this.roomID});
#override
_QuestionTileState createState() => _QuestionTileState(text);
}
class _QuestionTileState extends State<QuestionTile> {
final String text;
int netVotes = 0;
bool expand = false;
bool alreadyUpvoted = false;
bool alreadyDownvoted = false;
_QuestionTileState(this.text);
void toggleExpansion() {
setState(() => expand = !expand);
}
#override
Widget build(BuildContext context) {
RoomDbService dbService = RoomDbService(widget.roomName, widget.roomID);
final user = Provider.of<User>(context);
print(widget.text + " with questionID of " + widget.questionID);
return expand
? ExpandedQuestionTile(text, netVotes, toggleExpansion)
: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 7, 15, 7),
child: GestureDetector(
onTap: () => {
Navigator.pushNamed(context, "/ChatRoomPage", arguments: {
"question": widget.text,
"questionID": widget.questionID,
"roomName": widget.roomName,
"roomID": widget.roomID,
})
},
child: new Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
// the stack overflow functionality
children: <Widget>[
InkWell(
child: alreadyUpvoted
? Icon(Icons.arrow_drop_up,
color: Colors.blue[500])
: Icon(Icons.arrow_drop_up),
onTap: () {
dynamic result = dbService.upvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyUpvoted = !alreadyUpvoted;
if (alreadyDownvoted) {
alreadyDownvoted = false;
}
});
},
),
StreamBuilder<DocumentSnapshot>(
stream: dbService.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// print("Current Votes: " + "${snapshot.data.data["votes"]}");
// print("questionID: " + widget.questionID);
return Text("${snapshot.data.data["votes"]}");
}
},
),
InkWell(
child: alreadyDownvoted
? Icon(Icons.arrow_drop_down,
color: Colors.red[500])
: Icon(Icons.arrow_drop_down),
onTap: () {
dbService.downvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyDownvoted = !alreadyDownvoted;
if (alreadyUpvoted) {
alreadyUpvoted = false;
}
});
},
),
],
),
Container(
//color: Colors.red[100],
width: 290,
child: Align(
alignment: Alignment.centerLeft,
child: Text(text)), // problem solved if changed to Text(widget.text)
),
}
}
You can wrap your UI with a Stream Builder, this will allow the UI to update every time any value changes from Firestore.
Since you are using an item builder you can wrap the widget that is placed with the item builder.
That Should update the UI

In Dart/Flutter, how do I use a variable from a method so I can ouput it to a text field

Hope somebody can help - I hit this dead end a few weeks ago and think that I've tried everything within my limited knowledge.
I've set up a database that works OK - that is I can add data on one screen, review the data and edit the data on another screen. Now I want to sum one of the columns (beef) which I've been able to do as proven in the 'debugPrint' to the console. I want to access this variable 'beefTotal' from the 'sumBeef' method and print show this in a text field in the UI. I just can't manage it though. It just returns null.
Thanks in advance for any help.
import 'package:flutter/material.dart';
import 'package:take_note/utils/database_helper.dart';
class Info extends StatefulWidget {
#override
State<StatefulWidget> createState() => _InfoState();
}
DatabaseHelper helper = DatabaseHelper();
var database = DatabaseHelper();
class _InfoState extends State<Info> {
List beefTotal;
#override
Widget build (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Beef Info"),
backgroundColor: Colors.lightBlueAccent,
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: RaisedButton(
onPressed: (){
sumBeef();
},
),
),
),
Expanded(
child: Center(
child: Text("Total Beef is: £ $beefTotal", style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
fontWeight: FontWeight.w400
),),
),
),
],
),
)
);
}
void sumBeef () async {
beefTotal = await database.addBeef();
debugPrint("Total beef: $beefTotal");
}
}
The code below is from a class called DatabaseHelper which the method sumBeef() uses
Future<List<Map<String, dynamic>>> addBeef()async{
Database db = await this.database;
var result = await db.rawQuery("SELECT SUM(beef) FROM $table");
return result;
}
```[enter image description here][1]
[1]: https://i.stack.imgur.com/L46Gj.png
Just call
setState({
});
void sumBeef () async {
beefTotal = await database.addBeef();
setState(() {});
debugPrint("Total beef: $beefTotal");
}
and your good! anytime you make a change you have to call setState method to update the ui (rebuild) in flutters case