Flutter StreamBuilder return no data - flutter

I am trying to call the user first name in my drawer.
I already follow the suggestions in other question to use connection state, I use it already, but the result still null. The result when I try to call my drawer is 'Oops, there is no data'
Can someone show me which part is not correct? Really appreciate your help.
DatabaseService:
class DatabaseService {
final String uid;
final String taskId;
DatabaseService({this.uid, this.taskId});
//collection reference
CollectionReference userCollection = Firestore.instance.collection('user');
//get user data stream
Stream<UserData> get userData {
return userCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
//user data from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(fName: snapshot.data['first_name']);
}
} // DatabaseService
Drawer:
class DrawerWidget extends StatelessWidget {
final _auth = AuthService();
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
UserData userData = snapshot.data;
if (snapshot.hasData) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Drawer(...);
default:
return Drawer(
child: Center(child: Text(userData.fName)), //Try to call the data here
);
}
} else {
return Drawer(
child: Center(child: Text('Oops, there is no data')),
);
}
});
}
}
Data in Firebase:

Related

Flutter FutureBuilder stuck at ConnectionState.waiting

I'm new and currently learning Flutter, I have a question about FutureBuilder. My code is stuck at ConnectionState.waiting and I'm not sure why.
My codes are
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class DartPad extends StatefulWidget {
const DartPad({Key? key}) : super(key: key);
#override
State<DartPad> createState() => _DartPadState();
}
class _DartPadState extends State<DartPad> {
List three = [];
Future<dynamic> fetchData() async {
var sheetID = '1j7sYExCP0etGw_LoxqYTFRrjwHmJv73SHzC26jbtpH4';
var sheetTab = 'daftar';
var url = Uri.parse('https://opensheet.elk.sh/$sheetID/$sheetTab');
http.Response response;
response = await http.get(url);
setState(() {
three = json.decode(response.body);
});
}
#override
void initState() {
fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: true,
title: const Text('Dartpad'),
),
body: FutureBuilder<dynamic>(
future: fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text("${three[1]["nama"] ?? ''}");
}
}
},
),
);
}
}
I have tried to delete
case ConnectionState.waiting:
return Text('Loading....');
But received an error.
I have also tried to changed .hasData on snapshot (suggested from other thread) but the editor did not accept it.
I tried to not use switch and direcly used if but received RangeError (RangeError (index): Invalid value: Valid value range is empty: 1) error.
I want to run three[1]["nama"] (preferably in loop so I can display the rest of the data)
Thank you!
The reason this happened is that you are not return anything in fetchData, change fetchData to this:
Future<dynamic> fetchData() async {
var sheetID = '1j7sYExCP0etGw_LoxqYTFRrjwHmJv73SHzC26jbtpH4';
var sheetTab = 'daftar';
var url = Uri.parse('https://opensheet.elk.sh/$sheetID/$sheetTab');
http.Response response;
response = await http.get(url);
return json.decode(response.body);
}
then define new variable like this:
Future fetchFuture;
then use it like this inside initState:
#override
void initState() {
super.initState();
fetchFuture = fetchData();
}
then change your last return in your builder to this:
FutureBuilder<dynamic>(
future: fetchFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List data = snapshot.data ?? [];
return Text("${data[1]["nama"] ?? ''}");
}
}
},
)
There is nothing wrong with your code, it is your own mistake
Text("${three[1]["nama"] ?? ''}")
// Does your List really have two values?
// Is the Key value really 'nama'? Are you sure it's not a name?

Create ListView Builder from StreamBuilder with ISAR Database

I am trying to create a ListView Builder from Stream builder in Flutter.
TodoList Class
import 'package:isar/isar.dart';
part 'todo_list.g.dart';
#Collection()
class TodoList {
Id id = Isar.autoIncrement;
late String todoTitle;
}
Isar Services
class IsarService {
late Future<Isar> db;
IsarService() {
db = openDB();
}
//Return IsarDB, if not found, then create
Future<Isar> openDB() async {
if (Isar.instanceNames.isEmpty) {
return await Isar.open(
[TodoListSchema],
inspector: true,
);
}
return Future.value(Isar.getInstance());
}
Stream<List<TodoList>> listenToTodoList() async* {
final isar = await db;
yield* isar.todoLists.where().watch(fireImmediately: true);
}
}
ListView Builder from above Streambuilder
class ListScreen extends StatefulWidget {
final IsarService service;
const ListScreen(this.service, {super.key});
#override
State<ListScreen> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
//Text Controller
final _textController = TextEditingController();
final service = IsarService();
//Let's build List Title from snapshot
final List<TodoList> _todoList = [];
//Root widget of the class
#override
Widget build(BuildContext context) {
return Scaffold(
//List Screen Body Section
body: StreamBuilder(
stream: service.listenToTodoList(),
builder: (context, snapshot) {
if (snapshot.hasError) {
AlertDialog(
content: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
_todoList.add(snapshot.data); //Error happen in this line
}
return const CircularProgressIndicator();
},
),
);
}
}
The Error is
The argument type 'List?' can't be assigned to the parameter type 'TodoList'.
I'am trying to assign the snapshot data in final List<TodoList> _todoList = []; and use them ListView Builder
Try this code:
return Scaffold(
body: StreamBuilder(
stream: service.listenToTodoList(),
builder: (context, snapshot) {
if (snapshot.hasError) {
AlertDialog(
content: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
final todos = snapshot.data;
if (todos != null) {
return ListView.builder(
itemCount: todos.length();
itemBuilder: (context, index) {
final todo = todos[index];
return Text(todo.todoTitle);
}
);
} else {
return const Center(child: Text('No data found!'));
}
}
return const CircularProgressIndicator();
},
),
);

Error: A value of type 'Future<ListView>' can't be returned from a function with return type 'Widget'

I'm tryng to read data from Cloud Firestore (working) and put the data in a ListView containing a graphic widget (Order) that shows price, date and product, but when I'm trying to return the data from the method this error shows up.
The target is to return the data of the user passed to the db_utility constructor.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'order.dart';
class db_utility extends StatelessWidget {
final String userID;
const db_utility(this.userID, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder(
future: users.doc(userID).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("error");
} else if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("no data found");
} else if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return getListOfOrders();
}
return const Center(
child: CircularProgressIndicator(
color: Colors.red,
),
);
},
);
}
And this is the method that returns the ListView:
Future<ListView> getListOfOrders() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('completed_orders')
.where('user', isEqualTo: userID)
.orderBy('timestamp')
.get();
final data = snapshot.docs.map((doc) => doc.data()).toList();
List<Order> orders = <Order>[];
for (var o in data) {
orders.add(Order((o as Map)['price'], 'date of today', o['product']));
}
return ListView(children: orders,);
}
}
getListOfOrders() is another future method, you can use nested FutureBuilder .
In that case, instead of return getListOfOrders(); use another FutureBuilder like previous one. But you can do the operation on single future method.
You can also check multiple method on a future builder

The argument type 'Future<ListView>' can't be assigned to the parameter type 'Widget?'

I'm trying to create a ListView with some data received from Firebase, but I keep getting this message. I have tried with FutureBuilder but nothing was useful
The argument type 'Future' can't be assigned to the parameter type 'Widget?'
Code is here:
class CityScreen extends StatelessWidget {
const CityScreen({Key? key}) : super(key: key);
Future<ListView> CreateList() async {
List<CityButton>? listButtons;
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final uid = user!.uid;
CollectionReference trips = FirebaseFirestore.instance.collection('events');
QuerySnapshot eventsQuery =
await trips.where("uid", isEqualTo: uid).orderBy('initDate').get();
// ignore: avoid_function_literals_in_foreach_calls
eventsQuery.docs.forEach((element) {
listButtons!.add(CityButton(
element['city'],
DateTime.parse(element['initDate']),
DateTime.parse(element['endDate'])));
});
return ListView(
children: listButtons!,
);
}
#override
Widget build(BuildContext context) {
Future<ListView> lista = CreateList();
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: lista,
CreateList() method is a future, use FutureBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: FutureBuilder<ListView>(
future: CreateList(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return snapshot.data!;
} else {
/// you handle others state like error while it will a widget no matter what, you can skip it
return const CircularProgressIndicator();
}
},
),
When you want to render widget after async then use FutureBuilder()
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<ListView>(
future: CreateList(), // async work
builder: (BuildContext context, AsyncSnapshot<ListView> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return snapshot.data;
}
},
),
);
}

Flutter FutureBuilder snapshot returns Instance of 'Object' instead of data

i am new to flutter and trying to display data from a http post
referencing from [1]https://flutter.dev/docs/cookbook/networking/background-parsing and [2]https://flutter.dev/docs/cookbook/networking/fetch-data
i tried to display data on a futurebuilder but it keeps displaying this from the Text('${snapshot.data}')
[Instance of 'DashBoardBanner', Instance of 'DashBoardBanner', Instance of 'DashBoardBanner']
Builder
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<List<DashBoardBanner>> futureBanner;
#override
void initState() {
super.initState();
futureBanner = getBannerDataFromServer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ListView(
children: [
Card(
child: FutureBuilder(
future: getBannerDataFromServer(),
builder: (context,snapshot){
if(snapshot.connectionState == ConnectionState.done){
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
}
return const CircularProgressIndicator();
},
),
)
],
)),
);
}
}
Class and postreq
class DashBoardBanner {
final String MsgId;
final String MsgKey;
final String MsgPic;
const DashBoardBanner(
{required this.MsgId, required this.MsgKey, required this.MsgPic});
factory DashBoardBanner.fromJson(Map<String, dynamic> json) {
return DashBoardBanner(
MsgId: json['MsgId'] as String,
MsgKey: json['MsgKey'] as String,
MsgPic: json['MsgPic'] as String,
);
}
}
Future<List<DashBoardBanner>> getBannerDataFromServer() async {
final queryParameters = {
"ApiFunc": 'Banner',
"UserKey": getDeviceKey(),
"Token": getDeviceToken(),
"SubmitContent": json.encode({"MobileNo": getMobileNo1()})
};
final response = await http.post(
Uri.http('somesite.net', '/capi.aspx', queryParameters),
);
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
final splitoff = jsonEncode(data['RespContent']);
return compute(parseBanner, splitoff);
} else {
throw Exception('Failed to load Data');
}
}
List<DashBoardBanner> parseBanner(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed
.map<DashBoardBanner>((json) => DashBoardBanner.fromJson(json))
.toList();
}
Edit : i rebuilt the file replicating reference[1] and it finally displayed the data i needed, it seems the issue stem from not having this 2nd widget which return the obj back , however how do i combine the 2nd build widget into the first without needing the whole widget as having a whole build widget to return 1 line seems pointless?
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body:Container(
child: FutureBuilder<List<DashBoardBanner>>(
future: getBannerDataFromServer(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
print(snapshot.data!.length);
return DashBoardBannersList(dashboardBanners: snapshot.data!); <--- original issue due to not having this
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
class DashBoardBannersList extends StatelessWidget {
const DashBoardBannersList({Key? key, required this.dashboardBanners}) : super(key: key);
final List<DashBoardBanner> dashboardBanners;
#override
Widget build(BuildContext context) {
return Text(dashboardBanners[0].MsgId);
}
}
This error is caused because of the sound null safety
snapshot.data might be null for some requests so you can't access the array at a certain index cause it can be null.
If you know for sure snapshot.data exists you can use the ! operator to tell dart the variable is not null for sure like that:
snapshot.data![index];
You can also check if the data is null before accessing it like that:
if (snapshot.data != null) {
// do something with snapshot.data[index]
}
I recommed to read more about sound null safety here
Check the Firestore docs.
Inside snapshot.data, there's docs (every document of your collection).
The code is from there:
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['full_name']),
subtitle: Text(data['company']),
);
}).toList(),
);
},
);
}
The code above shows how to convert every doc (type DocumentSnapshot) to a JSON format (that can be represented with Map<String, dynamic>). To access to the doc id, you'll access with document.id, because it isn't inside the document.data() method.
You wanna retrieve a list of DashBoardBanner but you forget initialize the futurebuilder by adding a ListView.builder().
Try to use the following code idea :
FutureBuilder(
future: getBannerDataFromServer(http.Client()),
builder: (context, AsyncSnapshot snapshot) {
print(snapshot.hasData);
if (snapshot.hasError) {
return CircularProgressIndicator();
} else if (snapshot.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
var data = snapshot.data![index];
return DashBoardBannersList(dashboardBanners: data);
},),
),},
},)