Displaying app version, OS version and model in Flutter - asynchronous

I am using the device_info plugin to display the app version, OS version and model in Text widgets. I make three functions that each return a string.
//OS version
Future<String> _getVersionInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.version.release.toString();
}
//app version
Future<String> _getPackageInfo() async {
PackageInfo _packageInfo = await PackageInfo.fromPlatform();
return _packageInfo.version.toString();
}
Future<String> _getDeviceModel() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.model.toString();
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
return iosInfo.model.toString();
} else {
return ('Error retreiving device model');
}
}
Then I pass the function to the text Widget.
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('App Version: ${_getPackageInfo()}'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Device Model: ${_getDeviceModel()}'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('OS Version: ${_getVersionInfo()}'),
],
)
Instead of showing the returned string in the Text widget, it shows 'Instance of 'Future'. What am I doing wrong here?

These are asynchronous functions and need to be awaited. I would suggest using something like a FutureBuilder to render these properties.
FutureBuilder<String>(
future: _getDeviceModel(),
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Text('Device Model: ${snapshot.data}');
})

Related

Get array collection of an object from firebase flutter

i'm trying to fetch Products collection from specific user, and the request isn't working.
here is my code:
the first request function:
Stream<QuerySnapshot<Object>> get productsUser {
return usersCollection.doc(uid).collection("Products").snapshots();
}
and here where I try to present the Products array I fetch (or didn't...):
class _ProductPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GivitUser givitUser = Provider.of<GivitUser>(context);
final DatabaseService db = DatabaseService(uid: givitUser.uid);
return StreamBuilder<QuerySnapshot>(
stream: db.productsUser,
builder: (context, snapshotProduct) {
if (snapshotProduct.hasError) {
return Text('Something went wrong');
}
if (snapshotProduct.connectionState == ConnectionState.waiting) {
return Loading();
}
return Container(
color: Colors.blue[100],
height: 400.0,
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: snapshotProduct.data.docs.map(
(DocumentSnapshot document) {
var snapshotdata = document.data() as Map;
Product product =
Product.productFromDocument(snapshotdata, document.id);
print(product.name);
return Container(
child: Text(product.name),
);
},
).toList(),
),
),
);
});
}
}
);
Thanks to everyone who will help! :)
You can either create a StatefulWidget, and store the result of the fetch as state, or you can use a StreamBuilder to manage the state for you, and automatically rebuild the widget tree each time a new snapshot is received. In either case, the following two guides may also be helpful:
Streams
Async/Await
Here's an example of how you might use StreamBuilder in your case:
Widget build(BuildContext context) {
return Container(
color: Colors.blue[100],
height: 400.0,
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: StreamBuilder<QuerySnapshot<Object>>(
stream: usersCollection.doc(uid).collection("Products").snapshots(),
builder: (context, asyncSnapshot) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: asyncSnapshot.data.data.docs.map(
(DocumentSnapshot document) {
var snapshotdata = document.data() as Map;
Product product =
Product.productFromDocument(snapshotdata, document.id);
print(product.name);
return Container(
child: Text(product.name),
);
},
).toList(),
),
),
),
);
}

The method 'contains' was called on null

Hey guys this is my code: Im trying to display available time slots for my booking app.
Expanded(
child: FutureBuilder(
future: getTimeSlotOfCourt(
courtModel,
DateFormat('dd_MM_yyyy').format(context.read(selectedDate).state),
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
var listTimeSlot = snapshot.data as List<int>;
return GridView.builder(
itemCount: TIME_SLOT.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (context, index) => GestureDetector(
onTap: listTimeSlot.contains(index)
? null
: () {
context.read(selectedTime).state =
TIME_SLOT.elementAt(index);
context.read(selectedTimeSlot).state = index;
},
child: Card(
color: listTimeSlot.contains(index)
? Colors.white10
: context.read(selectedTime).state ==
TIME_SLOT.elementAt(index)
? Colors.white54
: Colors.white,
child: GridTile(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${TIME_SLOT.elementAt(index)}'),
Text(listTimeSlot.contains(index)
? 'Full'
: 'Available')
],
),
),
header: context.read(selectedTime).state ==
TIME_SLOT.elementAt(index)
? Icon(Icons.check)
: null,
),
),
));
}
},
),
)
],
);
}
I'm getting this error which says the method 'contains' was called on null.
Future<List<int>> getTimeSlotOfCourt(CourtModel courtModel, String date) async {
List<int> result = new List<int>.empty(growable: true);
// var ref = CourtModel().refer;
// var bookingRef = ref.collection(date);
var bookingRef = CourtModel().reference.collection(date);
QuerySnapshot snapshot = await bookingRef.get();
snapshot.docs.forEach((element) {
result.add(int.parse(element.id));
});
return result;
}
This is the function that I have used.
Please help me understand why I'm getting this error which says The method 'contains' was called on null.
The snapshot does not always contain data. The Future builder builds once before the future is completed. Null check the snapshot.data and return a spinner or something to fix this problem
The future builder only brings data when the future is completed and the snapshot may not contain data all the time. And you are only checking for the waiting state, a better solution would be to check whether the snapshot has data or not.
Something like this would be the preferred solution.
FutureBuilder(
future: _getTimeSlotOfCourt(),
builder:(context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// place you code here
}
}
)

Flutter/Dart - type 'List<DropdownMenuItem<dynamic>>' is not a subtype of type 'List<DropdownMenuItem<String>>

I am trying to create a drop down list inside an alert dialog widget. The menu items need to be pulled from firebase. So far, I have created my alert dialog, looped through my firebase data and created a list from the results. The issue I am facing comes when I try to use my list as the "items" for my dropdown, when I run my code I get the following error:
type 'List<DropdownMenuItem<dynamic>>' is not a subtype of type 'List<DropdownMenuItem<String>>'
Here is my code:
class ViewSingleCard extends StatefulWidget {
final String imgUrl;
final String message;
ViewSingleCard({this.imgUrl, this.message});
#override
_ViewSingleCardState createState() => _ViewSingleCardState(imgUrl, message);
}
class _ViewSingleCardState extends State<ViewSingleCard> {
String imgUrl;
String message;
_ViewSingleCardState(this.imgUrl, this.message);
PageController _pageController = PageController(initialPage: 0);
int currentPage = 0;
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
_onPageChanged(int index) {
setState(() {
currentPage = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurpleAccent,
title: Text('Viewer'),
actions: [
Stack(
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
createAlertDiaglog(context);
})
],
)
],
),
body: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
PageView(
scrollDirection: Axis.horizontal,
controller: _pageController,
onPageChanged: _onPageChanged,
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image(
image: FirebaseImage(imgUrl,
maxSizeBytes: 15 * 1024 * 1024))),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(message),
),
),
],
),
Stack(
children: <Widget>[
Container(
margin: const EdgeInsets.only(bottom: 35),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i <= 1; i++)
if (i == currentPage)
SlideDots(true)
else
SlideDots(false)
],
),
),
],
),
]),
);
}
createAlertDiaglog(BuildContext context) {
String selectedOccasion;
List<DropdownMenuItem> occasions = [];
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Add to collection"),
content: StreamBuilder<QuerySnapshot>(
stream: getCollectionInfo(context),
// ignore: missing_return
builder: (context, snapshot) {
if (!snapshot.hasData)
const Text("Loading.....");
else {
for (int i = 0; i < snapshot.data.docs.length; i++) {
DocumentSnapshot snap = snapshot.data.docs[i];
occasions.add(
DropdownMenuItem(
child: Text(
snap.id,
),
value: "${snap.id}",
),
);
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// ignore: missing_return
DropdownButton<String>(
items: occasions,
hint: Text("Style"),
value: selectedOccasion,
onChanged: (String Value) {
setState(() {
selectedOccasion = Value;
});
},
),
],
);
}),
);
});
}
Stream<QuerySnapshot> getCollectionInfo(BuildContext context) async* {
yield* FirebaseFirestore.instance
.collection('collections')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('occasions')
.snapshots();
}
}
Any help? Thanks
Here's the fix, add the <String> there:
occasions.add(
DropdownMenuItem<String>(
child: Text(
and also fix the type of the list (thanks to #nvoigt's answer)
List<DropdownMenuItem<String>> occasions = [];
Your DropDownButton is given the <String> type, so it's expecting the same thing from its items.
Whenever you get this exception, just swap the locations of the two types and think of an assignment. This means you are trying to do this kind of assignment
List<DropdownMenuItem<String>> a;
List<DropdownMenuItem<dynamic>> b;
a = b;
This:
List<DropdownMenuItem> occasions = [];
is a List<DropdownMenuItem<dynamic>>, but you want a List<DropdownMenuItem<String>>, so you need to make it one:
List<DropdownMenuItem<String>> occasions = [];
That said: you have an analyzer. Do not ignore it's warnings. You have ignored warnings that are correct, where you have made a mistake. Do not do this. Do not ignore your mistakes, fix them.

Reading data from Firebase - Flutter

I would like to change String to something else in order to read data from firebase.
I have the following code:
enum Answers{GS,FB}
String _value = ''; //Probably, String should be changed.
void _setValue(String value) => setState(() => _value = value); ////Probably, String should be changed.
Future kaleciSec(BuildContext context) async {
switch(
await showDialog(
...
...
StreamBuilder(
stream: Firestore.instance.collection('GS').snapshots(),
builder: (context, snapshot){
if(!snapshot.hasData) return Center (child: CircularProgressIndicator());
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
onTap: (){Navigator.pop(context, Answers.GS);}, //selecting GS.
title: Text(snapshot.data.documents[0]['kaleci1'], //showing data from DB
),
),
Divider(
color: Colors.grey,),
ListTile(
onTap: (){Navigator.pop(context, Answers.FB);},
title: Text(snapshot.data.documents[0]['kaleci2'],
),
],
);
},
),
],
),
)
)
{
case Answers.GS:
_setValue('GS'); //I would like connect database here.
break;
case Answers.FB:
_setValue('FB'); //I would like connect database here.
break;
}
My code works without an error. But, I need to show what is selected. Basically, I would like to change _setValue('GS'); to something like _setValue(snapshot.data.documents[0]['kaleci1']); this. Please help me!
I stick on this issue for a few days.
It seems that this is showing "kaleci1", because the _setValue(selected), is only reading the value of the variable and not the valeu from the database.
For you to return the value there, you would need to return the value of the database and add it to a new (or the same variable), so you can use in the _setValue() function.
I believe the code should be something like this:
enum Answers{GS,FB}
String _value = ''; //Probably, String should be changed.
var selected = 'kaleci1';
var valueDB;
void _setValue(String value) => setState(() => _value = value); ////Probably, String should be changed.
Future kaleciSec(BuildContext context) async {
switch(
await showDialog(
...
...
StreamBuilder(
stream: Firestore.instance.collection('GS').snapshots(),
builder: (context, snapshot){
if(!snapshot.hasData) return Center (child: CircularProgressIndicator());
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
onTap: (){Navigator.pop(context, Answers.GS);}, //selecting GS.
title: Text(snapshot.data.documents[0][selected], //showing data from DB
valueDB = snapshot.data.documents[0][selected]
),
),
Divider(
color: Colors.grey,),
ListTile(
onTap: (){Navigator.pop(context, Answers.FB);},
title: Text(snapshot.data.documents[0][selected],
valueDB = snapshot.data.documents[0][selected]
),
],
);
},
),
],
),
)
)
{
case Answers.GS:
_setValue(valueDB); //I would like connect database here.
break;
case Answers.FB:
_setValue(valueDB); //I would like connect database here.
break;
}
Please, let me know how that goes!

Using TextField inside a Streambuilder

How do we add a TextField inside a StreamBuilder?
I have a TextField / TextFormField as one of the widgets inside the builder function of either a StreamBuilder or FutureBuilder, whenever we try to interact with the textfield it just refreshes the entire builder widget and calls the stream/future again.
body: StreamBuilder(
stream: getClientProfile().snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
print(snapshot.data.data);
Client tempClient = Client.from(snapshot.data);
print('details = ${tempClient.representative.email} ${tempClient
.address.location} ${tempClient.businessDescription}');
return Container(
child: Column(
children: <Widget>[
TextFormField(
)
],
),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.error),
),
Text('Error loading data')
],
),
);
}
}),
and firestore function
DocumentReference getClientProfile() {
return _firestore.collection(SELLERS_COLLECTION).document(_uid);
}
What I want to achieve, is to have a form with pre-filled data from firestore document, basically an edit form. Is there any other way I could achieve the same or am I doing something wrong structurally ?
EDIT:
code after suggested edits.
import 'package:flutter/material.dart';
import 'Utils/globalStore.dart';
import 'models/client_model.dart';
import 'dart:async';
class EditProfileInformation extends StatefulWidget {
#override
EditProfileInformationState createState() {
return new EditProfileInformationState();
}
}
class EditProfileInformationState extends State<EditProfileInformation> {
Stream dbCall;
final myController = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
dbCall = getClientProfile().snapshots();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
#override
void dispose() {
myController.removeListener(_printLatestValue);
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
// key: _scaffoldKey,
appBar: AppBar(
title: Text(
'Edit profile',
style: TextStyle(),
),
),
body: StreamBuilder(
stream: dbCall,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
print(snapshot.data.data);
Client tempClient = Client.from(snapshot.data);
print('details = ${tempClient.representative.email} ${tempClient
.address.location} ${tempClient.businessDescription}');
return Container(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: myController,
),
)
],
),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.error),
),
Text('Error loading data')
],
),
);
}
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
},
child: Icon(Icons.done),
),
);
}
}
In order to use a StreamBuilder correctly you must ensure that the stream you are using is cached on a State object. While StreamBuilder can correctly handle getting new events from a stream, receiving an entirely new Stream will force it to completely rebuild. In your case, getClientProfile().snapshots() will create an entirely new Stream when it is called, destroying all of the state of your text fields.
class Example extends StatefulWidget {
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
Stream<SomeType> _stream;
#override
void initState() {
// Only create the stream once
_stream = _firestore.collection(collection).document(id);
super.initState();
}
#override
Widget build(BuildContext context) {
return new StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
...
},
);
}
}
EDIT: it sounds like there are other problems which I cannot diagnose from the code snippet you provided.

Resources