I am trying to get my first cloud firestore app working, using the firestore_flutter master example for cloud firestore as a template. In the example app main.dart has all the firestore and other init and access logic, and consequently runs the Futre void() main() method as in the example main.dart section copy below. It does not have the
====================
import...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd',
gcmSenderID: '79601577497',
apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI-G72PVU',
projectID: 'flutter-firestore',
),
);
final Firestore firestore = Firestore(app: app);
runApp(MaterialApp(title: 'Firestore Example', home: MyHomePage(firestore: firestore)));
}
class MyHomePage extends StatelessWidget {
MyHomePage({this.firestore});
final Firestore firestore;
CollectionReference get messages => firestore.collection('messages');
====================
But in my app I want to load the data in the 3rd page after authenticating users with firebase_auth, but the main Future function never gets called (only 1 main right?)
Here is my code that does not work because main() never gets called. What do I need to do to make the code below work? PS: I tried making it a called function, moving it, but I am not able to make main run in my page source below.
It doesn't have the next line from teh example in it, because it didn't help either.
runApp(MaterialApp(title: 'Firestore Example', home: TopicList(firestore: firestore)));}
Thanks for any help in advance.
====================
import ...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: 'AIzaSyBPq8j3NT5DMmVgXLEN3Z91QJK32ZhrH90',
gcmSenderID: '36424891892',
apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI',
projectID: 'usay-94b3a',
),
);
final Firestore firestore = Firestore(app: app);
}
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection("message").orderBy("timestamp", descending: true).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
final dynamic message = document['message'];
return ListTile(
trailing: IconButton(
onPressed: () => document.reference.delete(),
icon: Icon(Icons.delete),
),
title: Text(
message != null ? message.toString() : '<No message retrieved>',
),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
}
}
class TopicList extends StatelessWidget {
TopicList({this.firestore});
final Firestore firestore;
CollectionReference get messages => firestore.collection('message');
Future<void> _addMessage() async {
await messages.add(<String, dynamic>{
'messageTitle': 'Hello world!',
'messageDescription': 'Hello world!',
'timestamp': FieldValue.serverTimestamp(),
});
}
Future<void> _runTransaction() async {
firestore.runTransaction((Transaction transaction) async {
final allDocs = await firestore.collection("message").getDocuments();
final toBeRetrieved = allDocs.documents.sublist(allDocs.documents.length ~/ 2);
final toBeDeleted = allDocs.documents.sublist(0, allDocs.documents.length ~/ 2);
await Future.forEach(toBeDeleted, (DocumentSnapshot snapshot) async {
await transaction.delete(snapshot.reference);
});
await Future.forEach(toBeRetrieved, (DocumentSnapshot snapshot) async {
await transaction.update(snapshot.reference, {
// "messageTitle": FieldValue.messageTitle,
// "messageDescription": FieldValue.messageDescription",
"timestamp": FieldValue.serverTimestamp()
});
});
});
await Future.forEach(List.generate(2, (index) => index), (item) async {
await firestore.runTransaction((Transaction transaction) async {
await Future.forEach(List.generate(10, (index) => index), (item) async {
await transaction.set(firestore.collection("message").document(), {
"messageTitle": "Created from Transaction $item",
"messageDescription": "Created from Transaction $item",
"timestamp": FieldValue.serverTimestamp()
});
});
});
});
}
Future<void> _runBatchWrite() async {
final batchWrite = firestore.batch();
final querySnapshot = await firestore.collection("message").orderBy("timestamp").limit(12).getDocuments();
querySnapshot.documents.sublist(0, querySnapshot.documents.length - 3).forEach((DocumentSnapshot doc) {
batchWrite.updateData(doc.reference, {
"messageTitle": "Batched messageTitle",
"messageDescription": "Batched messageDescription",
"timestamp": FieldValue.serverTimestamp()
});
});
batchWrite.setData(firestore.collection("message").document(), {
"messageTitle": "Batched message created",
"messageDescription": "Batched message created",
"timestamp": FieldValue.serverTimestamp()
});
batchWrite.delete(querySnapshot.documents[querySnapshot.documents.length - 2].reference);
batchWrite.delete(querySnapshot.documents.last.reference);
await batchWrite.commit();
}
#override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
child: Scaffold(
appBar: AppBar(
title: const Text('Firestore Example'),
actions: <Widget>[
FlatButton(
onPressed: _runTransaction,
child: Text("Run Transaction"),
),
FlatButton(
onPressed: _runBatchWrite,
child: Text("Batch Write"),
)
],
),
body: MessageList(firestore: firestore),
floatingActionButton: FloatingActionButton(
onPressed: _addMessage,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
===============
Related
I started learning flutter isar yesterday and I couldn't love it more. I created a demo app with it and for some reason, it is not working as expected.
The app has two sections: Original(This contain the dummyData) and the Database(this contains data in the isar database).
When an item is starred in the original, it is added in the database and the icon is changed to filled_star. When the item is unstarred in the original section, it is expected to be removed from the database section and the icon is expected to change to star_outline. This is works fine.
However, when the app is hot-restarted, I am unable to unstar the items. Check the GIF below.
main.dart
import 'package:flutter/material.dart';
import 'package:isardemo/isar_files/course.dart';
import 'package:isardemo/isar_files/isar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blueGrey,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final dummyData = [
Course()
..title = 'MTH 212'
..courseId = '1ab',
Course()
..title = 'STS 432'
..courseId = '2bc',
Course()
..title = 'SHS 555'
..courseId = '3de',
Course()
..title = 'HMM 999'
..courseId = '4fg',
Course()
..title = 'EEE 666'
..courseId = '5hi',
];
Future<void> onFavTap(IsarService courseData, Course course) async {
if (await courseData.isItemDuplicate(course)) {
await courseData.deleteCourse(course);
setState(() {});
debugPrint('${course.courseId} deleted');
} else {
await courseData.addCourse(course);
setState(() {});
debugPrint('${course.courseId} added');
}
}
final courseData = IsarService();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Isar'),
),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
Center(
child: FutureBuilder(
initialData: courseData,
future: courseData.favoritesCount(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data.toString(),
style:
const TextStyle(fontSize: 25, color: Colors.lightGreen),
);
} else {
return const LinearProgressIndicator();
}
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
courseData.cleanDb();
setState(() {});
},
child: const Text('Destroy Database')),
),
const Text('Original',
style: TextStyle(fontSize: 30, color: Colors.green)),
ListView.separated(
shrinkWrap: true,
separatorBuilder: (context, index) => const SizedBox(height: 5),
itemCount: dummyData.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
tileColor: Colors.blueGrey,
title: Text(dummyData[index].title),
trailing: IconButton(
icon: FutureBuilder(
// initialData: courseData.isItemDuplicate(dummyData[index]),
future: courseData.isItemDuplicate(dummyData[index]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data) {
return const Icon(Icons.star);
} else {
return const Icon(Icons.star_border_outlined);
}
}
return const Icon(Icons.g_mobiledata,
color: Colors.green);
},
),
onPressed: () => onFavTap(courseData, dummyData[index])),
);
},
),
const SizedBox(height: 20),
const Text(
'database',
style: TextStyle(fontSize: 30, color: Colors.green),
),
FutureBuilder(
future: courseData.getAllCourses(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data![index].title),
trailing: InkWell(
onTap: () async {
await courseData
.deleteCourse(snapshot.data![index]);
setState(() {});
},
child: const Icon(Icons.star)),
);
},
);
} else {
return const Center(
child: LinearProgressIndicator(),
);
}
}),
],
),
);
}
}
course.dart
import 'package:isar/isar.dart';
part 'course.g.dart';
#Collection()
class Course {
Id id = Isar.autoIncrement;
late String courseId;
late String title;
late bool isFavorite = false; // new property
}
isar.dart
import 'package:path_provider/path_provider.dart';
import 'course.dart';
class IsarService {
late Future<Isar> _db;
IsarService() {
_db = openIsar();
}
Future<Isar> openIsar() async {
if (Isar.instanceNames.isEmpty) {
final directory = await getApplicationDocumentsDirectory();
return await Isar.open([CourseSchema],
inspector: true, directory: directory.path);
} else {
return await Future.value(Isar.getInstance());
}
}
Future<void> addCourse(Course course) async {
final isar = await _db;
await isar.writeTxn(() async {
await isar.courses.put(course);
});
}
Future<bool> isItemDuplicate(Course course) async {
final isar = await _db;
final count =
await isar.courses.filter().courseIdContains(course.courseId).count();
return count > 0;
}
Future<List<Course>> getAllCourses() async {
final isar = await _db;
return isar.courses.where().findAll();
}
Future<void> deleteCourse(Course course) async {
final isar = await _db;
await isar.writeTxn(() async {
await isar.courses.delete(course.id);
});
}
Future<String> favoritesCount() async {
final isar = await _db;
final count = await isar.courses.count();
return count.toString();
}
Future<void> cleanDb() async {
final isar = await _db;
await isar.writeTxn(() => isar.clear());
}
}
I tried downgrading the isar version but it didn't work.
I fixed it! Instead of auto-incrementing the id, I used the Course's id instead. But Id expects an integer so I had to convert the Course's id into an integer using the fastHash.
Learn more
course.dart
#Collection()
class Course {
late String id;
Id get courseId => fastHash(id);
late String title;
}
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
I have problem with async-await. (I am not very good at programming, but learning by creating random apps...)
Problem: Using dio to get data from Node.js json-server, but I cant transform data from
Future to List. Error: type 'Future' is not a subtype of type 'List' at line 13. List<Routes> routes = _getData();
I have read a lot of discussions here on stackoverflow and many other websites, but I just can't make it work. :( So here I am asking with specific code.
Needed code:
Code where error appears (route_list_screen.dart)
import 'package:app/api/api.dart';
import 'package:flutter/material.dart';
import 'package:app/models/routes.dart';
class RouteList extends StatefulWidget {
const RouteList({Key? key}) : super(key: key);
#override
State<RouteList> createState() => _RouteListState();
}
List<Routes> routes = _getData();
class _RouteListState extends State<RouteList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Text'),
automaticallyImplyLeading: true,
centerTitle: true,
),
body: ListView.separated(
itemCount: routes.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes[index].number),
subtitle: Text(routes[index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes[index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
_getData() async {
Future<dynamic> futureOfRoutes = getRouteList(856);
List<dynamic> routes = await futureOfRoutes;
return routes;
}
Connecting to server (api.dart)
import 'package:app/models/routes.dart';
const _url = 'http://10.0.2.2:3000/routes';
getRouteList(int driverId) async {
Response response;
var dio = Dio(BaseOptions(
responseType: ResponseType.plain,
));
response = await dio.get(_url, queryParameters: {"driver_id": driverId});
final data = routesFromJson(response.data);
return data;
}
List with param Routes = Routes is model from app.quicktype.io
_getData() returns a future, you can't direct assign it on List<Routes> where it is Future<dynamic>.
You can use initState
class _RouteListState extends State<RouteList> {
List<Routes>? routes;
_loadData() async {
routes = await _getData();
setState(() {});
}
#override
void initState() {
super.initState();
_loadData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: routes == null
? Text("On Future ....")
: ListView.separated(
itemCount: routes?.length??0,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes![index].number),
subtitle: Text(routes![index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes![index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
Also check FutureBuilder
I was making a simple cart app, it did well but cart count not showing when app is closed and reopened again.
I am using provider and calls fetchCartProducts() method when the app is opened. It calls fine. but cart badge widget itemcount is not changing at first time. only shows 0 at first time.
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
print('inside fetchcart');
}
class HomeScreen extends StatefulWidget
{
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
{
Future<List<FoodItem>> _foodItems;
var _isInit = true;
#override
void initState() {
super.initState();
_foodItems = ApiService.getFoodItems();
Provider.of<CartProvider>(context, listen: false).fetchCartProducts();
setState(() {});
}
#override
void didChangeDependencies()
{
if (_isInit) {
Provider.of<CartProvider>(context).fetchCartProducts();
_isInit = false;
setState(() {});
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final cart = Provider.of<CartProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Food Cart'),
actions: [
//this is not updating when the app is closed and opened again.
Consumer<CartProvider>(
builder: (_, cartprovider, ch) => Badge(
child: ch,
value: cartprovider.itemCount.toString(),
),
child: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return CartScreen();
}),
);
},
),
),
],
),
body: FutureBuilder<List<FoodItem>>(
future: _foodItems,
builder: (conext, snapshot) => !snapshot.hasData
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
FoodItem foodItem = snapshot.data[index];
return ListTile(
title: Text(foodItem.productName),
subtitle: Text(foodItem.variant),
trailing: IconButton(
onPressed: () {
cart.addToCart(
foodItem.storeid.toString(),
foodItem.productName,
1,
foodItem.price,
);
setState(() {});
},
icon: const Icon(Icons.shopping_cart),
),
);
},
),
),
);
}
}
otherwise when item added to cart, it working fine. the data loss when reopened. how to get total count when the app starts?
In order to rebuild Consumer you need to call notifyListeners() inside your CartProvider
Add notifyListeners() to your fetchCartProducts() after assigning the value to _cartItems = Map<String, CartModel>.fromEntries(entries);
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
notifyListeners(); // <------- this line
print('inside fetchcart');
}
I was create SharedPreferences to save user loading in logon page. Then data of user will be save in SharedPreferences and move to main page. But my problem now in main page I need use this variable in different places in main page. But I cant do that.
I need to make variable of logindata can use in each places in main page I try to use in drawer to make logout. No I get error as:
Undefined name 'logindata'.
this is my code:
void initial() async {
logindata = await SharedPreferences.getInstance();
setState(() {
username = logindata.getString('username');
return username;
});
}
my full code:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'addnewtopics.dart';
import 'DetilesOfMainPage.dart';
import 'loginpage.dart';
class MyApp extends StatelessWidget {
final String email;
MyApp({Key key, #required this.email}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('JSON ListView')
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
logindata.setBool('login', true);// here I need to use It ========================
Navigator.pushReplacement(context,
new MaterialPageRoute(builder: (context) => LoginUser()));
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Navigator.pop(context);
},
),
],
),
),
body: JsonImageList(),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => UploadImageDemo()
),);
},
child: Icon(Icons.add),
),
));
}
}
class Flowerdata {
int id;
String flowerName;
String flowerImageURL;
Flowerdata({
this.id,
this.flowerName,
this.flowerImageURL
});
factory Flowerdata.fromJson(Map<String, dynamic> json) {
return Flowerdata(
id: json['id'],
flowerName: json['nametopics'],
flowerImageURL: json['image']
);
}
}
class JsonImageList extends StatefulWidget {
JsonImageListWidget createState() => JsonImageListWidget();
}
class JsonImageListWidget extends State {
SharedPreferences logindata;
String username;
#override
void initState() {
// TODO: implement initState
super.initState();
initial();
}
void initial() async {
logindata = await SharedPreferences.getInstance();
setState(() {
username = logindata.getString('username');
return username;
});
}
final String apiURL = 'http://xxxxxxxxx/getFlowersList.php';
Future<List<Flowerdata>> fetchFlowers() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Flowerdata> listOfFruits = items.map<Flowerdata>((json) {
return Flowerdata.fromJson(json);
}).toList();
return listOfFruits;
}
else {
throw Exception('Failed to load data from Server.');
}
}
getItemAndNavigate(String item, BuildContext context){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(itemHolder : item)
)
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Flowerdata>>(
future: fetchFlowers(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(
child: CircularProgressIndicator()
);
return ListView(
children: snapshot.data
.map((data) => Column(children: <Widget>[
GestureDetector(
onTap: ()=>{
getItemAndNavigate(data.flowerName, context)
},
child: Row(
children: [
Container(
width: 200,
height: 100,
margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child:
Image.network(data.flowerImageURL,
width: 200, height: 100, fit: BoxFit.cover,))),
Flexible(child:
Text(data.flowerName,
style: TextStyle(fontSize: 18)))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
},
);
}
}
Anyone know how can make that?
You need var keyword, in your case you can directly use
var logindata = await SharedPreferences.getInstance();
You do not need to make it global, because SharedPreferences.getInstance() is Singleton
Every time you use var logindata = await SharedPreferences.getInstance(); will get the same instance
Also there is no performance issue when you call getInstance(), because it's cached, you can see source code snippet below
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
_completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
_completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = _completer.future;
_completer = null;
return sharedPrefsFuture;
}
}
return _completer.future;
When you declare a String outside of class and does not contain _ before variable name like _localString it become global
String globalString = ""; //global, import can be seen
String _localString = ""; //local and can only be seen in this file, import can not seen
void main() async{
var logindata = await SharedPreferences.getInstance();
runApp(MyApp());
}
You simply need to put your variable outside of any class or method. An example is to create a file globals.dart then put all your globals in it and import the file when you need.
Example
// globals.dart
String globalString;
int globalInt;
bool globalBool;
// in any other file
import 'globals.dart' as globals;
globals.globalString = "Global String";
Using the example here.
I modified some code to output the the message onTap as follows:
The List is generated with real values from firestore.
but the output is blank. It's not empty, as I tested with another set of output.
How can I retrieve these real values to use in onTap event handler?
....
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
final dynamic message = document['message'];
return ListTile(
title: Text(
message != null ? message.toString() : '<No message retrieved>',
),
subtitle: Text('Message ${index + 1} of $messageCount'),
onTap: (){
print(message.toString());
// OUTPUT: (Nothing)
},
);
},
);
....
onTap: (){
print(message.toString().isEmpty ? 'Empty' : 'Not Empty');
//OUTPUT: Not Empty
print(message== null ? 'Null' : 'Not Null');
//OUTPUT: Not Empty
},
The reason you don't get your variable printed in onTap may lie somewhere else, e.g. in that you are possibly running other version of your app than your current code corresponds to. Try hard resetting the app and make sure you refresh the code on the emulator. Don't open the app manually from the emulator, it can load older version of your code.
I recreated exact the same code from Flutter example and it works perfectly fine (message gets printed correctly when tapping on a list tile as expected).
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final Firestore firestore = Firestore();
runApp(MaterialApp(
title: 'Firestore Example', home: MyHomePage(firestore: firestore)));
}
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore
.collection("messages")
.orderBy("created_at", descending: true)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
final dynamic message = document['message'];
return ListTile(
trailing: IconButton(
onPressed: () => document.reference.delete(),
icon: Icon(Icons.delete),
),
title: Text(
message != null ? message.toString() : '<No message retrieved>',
),
subtitle: Text('Message ${index + 1} of $messageCount'),
onTap: () {
print(message.toString());
// THIS ACTUALLY WORKS !!
},
);
},
);
},
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({this.firestore});
final Firestore firestore;
CollectionReference get messages => firestore.collection('messages');
Future<void> _addMessage() async {
await messages.add(<String, dynamic>{
'message': 'Hello world!',
'created_at': FieldValue.serverTimestamp(),
});
}
Future<void> _runTransaction() async {
firestore.runTransaction((Transaction transaction) async {
final allDocs = await firestore.collection("messages").getDocuments();
final toBeRetrieved =
allDocs.documents.sublist(allDocs.documents.length ~/ 2);
final toBeDeleted =
allDocs.documents.sublist(0, allDocs.documents.length ~/ 2);
await Future.forEach(toBeDeleted, (DocumentSnapshot snapshot) async {
await transaction.delete(snapshot.reference);
});
await Future.forEach(toBeRetrieved, (DocumentSnapshot snapshot) async {
await transaction.update(snapshot.reference, {
"message": "Updated from Transaction",
"created_at": FieldValue.serverTimestamp()
});
});
});
await Future.forEach(List.generate(2, (index) => index), (item) async {
await firestore.runTransaction((Transaction transaction) async {
await Future.forEach(List.generate(10, (index) => index), (item) async {
await transaction.set(firestore.collection("messages").document(), {
"message": "Created from Transaction $item",
"created_at": FieldValue.serverTimestamp()
});
});
});
});
}
Future<void> _runBatchWrite() async {
final batchWrite = firestore.batch();
final querySnapshot = await firestore
.collection("messages")
.orderBy("created_at")
.limit(12)
.getDocuments();
querySnapshot.documents
.sublist(0, querySnapshot.documents.length - 3)
.forEach((DocumentSnapshot doc) {
batchWrite.updateData(doc.reference, {
"message": "Batched message",
"created_at": FieldValue.serverTimestamp()
});
});
batchWrite.setData(firestore.collection("messages").document(), {
"message": "Batched message created",
"created_at": FieldValue.serverTimestamp()
});
batchWrite.delete(
querySnapshot.documents[querySnapshot.documents.length - 2].reference);
batchWrite.delete(querySnapshot.documents.last.reference);
await batchWrite.commit();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firestore Example'),
actions: <Widget>[
FlatButton(
onPressed: _runTransaction,
child: Text("Run Transaction"),
),
FlatButton(
onPressed: _runBatchWrite,
child: Text("Batch Write"),
)
],
),
body: MessageList(firestore: firestore),
floatingActionButton: FloatingActionButton(
onPressed: _addMessage,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
TLDR: Updating the database values worked.
This must be the most weirdest thing I have seen.
But instead of the Text itself, I tried printing the length of it and it has same length as the text it had in ListTile.
Then I checked the firebase database. My findings:
Data was visible
Plain text - English
Selecting the data and copy pasting it on a notepad pasted Nothing
Retyping the data made the app to work as expected.
I didn't think of it before, but how the data was initially added to database might have been the issue.