How to Display data being read from Firestore - android

void _myMatches() {
if (SignUp.userUid != null) {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: SignUp.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
} else {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: Loginpage.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
}
}
}
Hi, I am using flutter and firestore to write a program. My function that reads the data is as follows:(mentioned above)
which i call when a specific button is pressed. This leads to the data being read from firestore to be printed on the console. What do I do to display it on my emulator. How do I wrap this data in a widget so I can display it on the screen on whichever page i want?

The key is to use a FutureBuilder to render UI after you get the data, and show loading before that. Then inside builder of FutureBuilder, use ListView and ListTile(or anything you like) to render list items.
A minimum example might looks like this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
home: App(),
));
}
class App extends StatelessWidget {
Future<QuerySnapshot<Map<String, dynamic>>> getData() {
// Handle any data retrieval logic you want
return FirebaseFirestore.instance.collection('posts').get();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
// plug your future snapshot here
future: getData(),
builder: (context, snapshot) {
// Check loading
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// Check error
final queryData = snapshot.data;
if (snapshot.hasError || queryData == null) {
return Icon(Icons.error);
}
return Scaffold(
// Use ListView.builder to render only visible items
body: ListView.builder(
itemCount: queryData.docs.length,
itemBuilder: (context, index) {
// Get data inside docs
final docData = queryData.docs[index].data();
return ListTile(
title: docData['title'],
subtitle: docData['subtitle'],
);
},
),
);
});
}
}

Related

Flutter: Stream is not updating data automatically (only after hot reload)

Problem: Both of my streams from the code below do not update my UI automatically.
So the new data is only fetched and displayed when I do a hot reload or a hot restart. I am trying to fetch the most recent messages from each chat room and display them to the user.
Question: How can I change my code to make the streams work properly? Or is there maybe a better solution to what I am doing below?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:blabber_tech/services/auth.dart';
import 'package:blabber_tech/services/chat_services.dart';
class MyChatsScreen2 extends StatelessWidget {
static const String id = "mychats2_screen";
// get current user id
String? userId = AuthService().getUserId();
// Stream of all rooms of current user
Stream getRoomsStream() async* {
// get rooms of current user
QuerySnapshot roomsSnapshot = await FirebaseFirestore.instance
.collection("rooms")
.where("userId1", isEqualTo: userId)
.get();
// get rooms of current user
QuerySnapshot roomsSnapshot2 = await FirebaseFirestore.instance
.collection("rooms")
.where("userId2", isEqualTo: userId)
.get();
// add rooms of current user to rooms list
List<QueryDocumentSnapshot> rooms = roomsSnapshot.docs;
// add rooms of current user to rooms list
List<QueryDocumentSnapshot> rooms2 = roomsSnapshot2.docs;
// add rooms of current user to rooms list
rooms.addAll(rooms2);
// sort rooms list by when last message was sent
// rooms.sort(
// (a, b) => b["lastMessageSentAt"].compareTo(a["lastMessageSentAt"]));
yield rooms;
}
// Stream to get last message of each room
Stream getLastMessageStream(String roomId) async* {
try {
// get last message of room
QuerySnapshot lastMessageSnapshot = await FirebaseFirestore.instance
.collection("rooms")
.doc(roomId)
.collection("messages")
.orderBy("createdAt", descending: true)
.limit(1)
.get();
// get last message of room
List lastMessage = lastMessageSnapshot.docs;
// return last message of room
yield lastMessage;
} catch (error) {
print(error);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
// create listview of all chats of current user and show last message and other user name and photo
child: StreamBuilder(
stream: getRoomsStream(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return StreamBuilder(
stream: getLastMessageStream(snapshot.data[index].id),
builder: (context, AsyncSnapshot<dynamic> snapshot2) {
if (snapshot2.hasData) {
return ListTile(
leading: CircleAvatar(
//backgroundImage: NetworkImage(
//snapshot.data[index]["userPhotoUrl"]),
),
//title: Text(snapshot.data[index]["userName"]),
subtitle: Text(snapshot2.data[0]["message"]),
);
} else {
return Container();
}
},
);
},
);
} else {
return Container();
}
},
),
),
);
}
}
Since you're using get() when the widget is created, the data is only loaded from the database once when the widget is created. If you want to get the new data whenever it is updated, use a snapshot() listener - which returns a stream which gets an initial event with the initial data, and a new event whenever the data is updated.
To wire the Stream up in your build method, you'll want to use a StreamBuilder as shown in the Firebase documentation on listening for realtime updates in Flutter.

Why is setState function in flutter not working?

I am working with APIs and using a delete method to delete an item with a unique id from a list. The delete method is working but I need to reload the page everytime I want to see the results. I tried to add a setState() function inside a button and call the delete method from there but it is not working. I am not getting any errors however.
Delete method:
Future <void> deleteData(todo) async {
var urlToUpdate = Uri.parse('https://todoapp-api.apps.k8s.gu.se/todos/${todo.id}?key=${testKey}');
try {
await http.delete(urlToUpdate, headers: {"Content-Type": "application/json"}, body: jsonEncode({
"id": todo.id,
"title": todo.title,
"done": todo.done
}));
} catch (err) {
print(err);
}
}
setState method:
child: IconButton(
onPressed: () {
setState(() {
var deleteTodo = TodoItem(id: id, title: '', done: false);
deleteData(deleteTodo);
});
},
I can't provide the whole code because it is too large but the delete method comes right after :
class _TodoListState extends State {
and before initState and Widget build.
My TodoItemsList works like this:
Future fetchPosts() async {
try {
await getKey();
final response = await HTTP.get(Uri.parse('${url}${todos}${testKey}'));
final jsonData = jsonDecode(response.body);
setState(() {
TodoItemsList = jsonData;
});
print(jsonData);
} catch (err) {
print('Error');
}
}
This empty list is just above the Widget build
List TodoItemsList = [];
This widget is inside by body property:
Widget getBody() {
return ListView.builder(
itemCount: TodoItemsList.length,
itemBuilder: (context, index) {
return getCard(TodoItemsList[index]);
});
}
you should wait until the deleteData finished.
After that, remove the local TodoItem from the list by yourself.
child: IconButton(
onPressed: () async {
var deleteTodo = TodoItem(id: id, title: '', done: false);
await deleteData(deleteTodo);
setState(() {
=> remove TodoItem from the local list =<
});
}
Because network request usually takes times. You should display something like CircularProgressIndicator when deleteData is running. But that's another story.
setState method is used to reflect any change of data over some widget, if you need to remove a element from a list need has that element linked to a widget
example:
If you has
listOfMovie = ['Avatar, Avengers', 'Dune', 'Hulk'];
ListView.builder(
itemCount: listOfMovie.length,
itemBuilder: (_, index) => Text(listOfMovie[index],
));
then
child: IconButton(
onPressed: () {
setState(() {
listOfMovie = ['Avatar, Avengers'];
});
},
If you notice listOfMovie is linked to ListView widget

Why the auto login does not work in flutter

I do not want to use accesstoken or refreshtoken but it is not working. Does anyone help me to handle this situation? Here is my future =>
Future<bool> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var user = prefs.getString('username');
var pwd = prefs.getString('password');
debugPrint(user.toString());
debugPrint(pwd.toString());
UserResponse account = await LoginApi().login(user, pwd);
// debugPrint(account.toString());
bool acc = prefs.containsKey('userInfo');
return acc;
}
This is my usage part =>
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null){
Future.delayed(Duration(milliseconds: 1000), () {
setState(() {
snapshot.data == false;
});
});
return LoadingScreen();
}
return snapshot.data == true ? MainWidget() : LoginScreen();
},
),
When i first time to try login it stays on loading screen, snapshot.data stays null. It does not change.
Create an instance at widget that is int ctr=0;
Then you can change there like this:
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null) {
ctr++;
if(ctr>1) {
return LoginScreen();
}
else {
return LoadingScreen();
}
}
return snapshot.data == true ? MainWidget() : LoginScreen();
Your Future.delayed() is not being awaited, so the time delay is effectively a no-op except to push that setState later.
When the layers start to be complicated with a series of async operations, it's time to look at something like RiverPod (which I prefer over Provider and BLoC) to give you wrappers around those async operations, which properly cascade the dependencies in a readable format.

Refreshing or rebuilding flutter widget after it has been fully initialised

I am trying to make a Telegram client for android using the tdlib flutter port. I am currently attempting to make a contact list of sorts, by requesting it from telegram and making a listview of textbuttons.
The only issue is that since the library is async, I get the contact list after the layout has been initialized. Is it possible to somehow rebuild the layout or update it to make the list load properly.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fima/services/telegram_service.dart';
import 'package:tdlib/td_api.dart' show TdError;
import 'package:provider/provider.dart';
import 'package:tdlib/td_api.dart' as TdApi;
class ContactListScreen extends StatefulWidget {
#override
_ContactListScreenState createState() => _ContactListScreenState();
}
class _ContactListScreenState extends State<ContactListScreen> {
final String title = 'Contact list';
bool _loadingStep = false;
String _Error;
String route = "initRoute";
List<TextButton> contacts = [];
#override
void initState() {
super.initState();
_getContacts(onError: _handelError,);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Color(0xD3232323),
),
body: Container(
child:
ListView (
children: contacts,
),
),
);
}
Future _getContacts(
{
void Function(TdError) onError,
}) async {
final result = await context.read<TelegramService>().send(
TdApi.GetContacts(
),
);
if (result is TdError && onError != null) {
onError(result);
}
TdApi.Users users = result;
for (var i = 0; i < users.totalCount; i++) {
final result = await context.read<TelegramService>().send(
TdApi.GetUser(userId: users.userIds[i]),
);
TdApi.User user = result;
print(user.firstName + " " + user.lastName);
final contact = TextButton(
onPressed: () {
print("Test");
},
child: Text(user.firstName + " " + user.lastName),
);
setState(() {
contacts.add(contact);
});
}
}
void _handelError(TdError error) async {
setState(() {
_loadingStep = false;
_Error = error.message;
});
}
}
I have attempted to use setState, but without much success, could anyone be so kind as to provide me with the solution to this problem?
Using the FutureBuilder might help. It is a widget that builds itself based on the latest snapshot of interaction with a Future.
You can modify your build to return a FutureBuilder something like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getContacts,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
//Use snapshot data to build by returning your Container with List
}
else{
//Return a CircularProgressIndicator
}
}
}
Refer the documentation on the FutureBuilder class here.

Flutter asset database error: The getter 'length' was called on null. Receiver: null Tried calling: length

I added my existing database.db file to my project with sqflite. No errors encountered, everything works fine, but... Flutter debug console says:
Restarted application in 772ms.
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<List<Countries>>(dirty, state: _FutureBuilderState<List<Countries>>#d0317):
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
FutureBuilder<List<Countries>>
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 _HomeScreen.buildBody.<anonymous closure>
#2 _FutureBuilderState.build
#3 StatefulElement.build
#4 ComponentElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
I/flutter (14052): Opening existing database
Here is my model Country.dart :
class Countries {
int countryId;
String countryName;
String countryImageURL;
//Constructor
Countries({this.countryId, this.countryName, this.countryImageURL});
// Extract a Product Object from a Map Oject
Countries.fromMap(Map<String, dynamic> map) {
countryId = map['country_id'];
countryName = map['country_name'];
countryImageURL = map['image'];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'country_name': countryName,
'image': countryImageURL
};
return map;
}
}
Here is my database_helper.dart file:
import 'dart:async';
import 'dart:io';
import 'package:city_travel_guide/model/Country.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
class DbHelper {
static Database _db;
Future<Database> get db async {
if (_db != null) {
return _db;
} else {
_db = await initDb();
return _db;
}
}
initDb() async {
var dbFolder = await getDatabasesPath();
String path = join(dbFolder, 'app.db');
var exists = await databaseExists(path);
if (!exists) {
// Should happen only the first time you launch your application
print("Creating new copy from asset");
// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// Copy from asset
ByteData data = await rootBundle.load(join("assets", "example.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print("Opening existing database");
}
// open the database
return await openDatabase(path);
}
Future<List<Countries>> getCountries() async {
var dbClient = await db;
var result = await dbClient.query('Country', orderBy: 'countryId');
return result.map((data) => Countries.fromMap(data)).toList();
}
Here is my main.dart file:
import 'package:city_travel_guide/data/database_helper.dart';
import 'package:city_travel_guide/model/Country.dart';
import 'package:flutter/material.dart';
import 'widgets/maindrawer.dart';
import 'pages/search.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'City Travel Guide',
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: MyHome());
}
}
class MyHome extends StatefulWidget {
#override
_HomeScreen createState() => _HomeScreen();
}
class _HomeScreen extends State<MyHome> {
List<Countries> countries;
final dbHelper = DbHelper();
#override
void initState() {
dbHelper.initDb();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'City Travel Guide',
style: Theme.of(context).primaryTextTheme.headline6,
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SearchScreen()),
);
}),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
drawer: Drawer(child: MainDrawer()),
body: buildBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
));
}
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
});
}
}
How can I list items on my asset database and view it in application?
FutureBuilder is an asynchronous request. Always check that snapshot has data before building your list.
do:
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.length > 0) // This ensures that you have at least one or more countries available.
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
else if (snapshot.hasData && snapshot.data.length == 0)
return Center(child:Text("There are no countries available"));
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor),
)); // This would display a loading animation before your data is ready
});
}
Its to easy you must check if there is a data comming from the future or not before using the snapshot.data.lenght because if the snapshot.data is null(the opperation not finished yet) then lenght was calling on null so you must do it
The correct code
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if(snapshot.hasdata&&snapshot.data.runtimetype==List){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
}else{
return Proggresindicator()//or any loading widgets
}
});
}
}
and you can add check for any execption happens during the future

Categories

Resources