How to refresh a widget with stream builder in flutter - android

I am trying to show data from the text file as per the data stored in shared preference i have another screen to save data in the text file i have a stream builder earlier it was future builder So i am trying to refresh the screen when coming back from second screen i tried to call a method when pop the method is getting called in the viewmodel calss of provider but the streambuilder is not getting updated
this is the code
to fetch data
Future<List<String>> fetchdata() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? category = prefs.getString('category');
if (category != null) {
lines = await locator<JsonAPI>().fetchquotes(category);
} else {
lines = await locator<JsonAPI>().fetchquotes('quotes');
}
// data = lines as Future<List<String>>;
notifyListeners();
return lines;
}
stream builder
var quotesdata = Provider.of<HomeViewModel>(context, listen: false);
StreamBuilder(
stream: quotesdata.fetchdata().asStream(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<String> lines = quotesdata.lines;
// List<String>? lines = snapshot.data as List<String>?;
return ScreenShotWidget(
homeViewModel: quotesdata,
list: lines,
);
} else {
return Container();
}
}),
method that i call when pop
function(data) {
category = data.toString();
fetchdata();
notifyListeners();
setState() {}
}
any idea how to update the screen

Every time your widget rebuilds, you get a new stream. This is a mistake. You should obtain the stream only once (for example, in initState)
#override
void initState() {
_stream = quotesdata.fetchdata().asStream();
}
and use that stream variable with StreamBuilder
StreamBuilder(
stream: _stream,
Later, when you want to update the stream, you can do
setState(() {
_stream = quotesdata.fetchdata().asStream();
})
to change the stream and force a refresh.
Please go over your code and change all such usages
StreamBuilder(
stream: quotesdata.fetchdata().asStream(),
to this kind of usage.
StreamBuilder(
stream: _stream,
Otherwise you may get a high backend bill someday. Right now every screen refresh does a new query to the backend.

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.

Async await does not wait for the output to return

I am making an bungalow reservation system with spring rest back end and flutter front end.
In this I want to get a list of bungalows.
So I decided to make a method to get the list of bungalows in a method using HttpService class that I made to handle the rest end points, That method is getBungalows() method.
Then I called this method by overriding initstate().
But the problem is that before my initstate() is completed. my build method starts.
To prove this I printed two lines 'print' and 'print build' as I thought I get 'print build' first. what am I doing wrong here. Please help.
Method to retrieve data from rest back end
When this happened I first checked this method but this works fine and return the desired result.
Future<List<Bungalow>> getBungalows() async {
Uri uri = Uri.parse('$url/bungalows/');
http.Response response = await http.get(uri);
if (response.statusCode == 200) {
List<Bungalow> bungalows = List<Bungalow>.from(
json.decode(response.body).map((x) => Bungalow.fromJson(x)));
// print(bungalows.first.address + 'asafafasfafdfgfgarfgargafvfrvaerg');
return bungalows;
} else {
throw 'Unable to retrieve data';
}
}
Code of the HomeScreen
class _HomeScreenState extends State<HomeScreen> {
HttpService httpService = HttpService();
late List<Bungalow> bungalows;
bool isLoggedIn = false;
User? user;
void getBungalows() async {
bungalows = await httpService.getBungalows();
print('done');
}
#override
Widget build(BuildContext context) {
if (widget.user != null) {
isLoggedIn = true;
user = widget.user;
}
print('done build');
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Text(isLoggedIn ? user!.userDetail.username : 'No login'),
// TextButton(
// onPressed: () {
// setState(() {
// getBungalows();
// print(bungalows.first.address);
// });
// },
// child: Text('click'))
],
),
);
}
#override
void initState() {
getBungalows();
}
}
Console Output
I/flutter (22248): done build
I/flutter (22248): done
It is behaving correctly, initState function is not async and method getBungalows() is called in parallel.
You should either use setState in getBungalows, or add a listener, or use the then keyword, or use StreamBuilder.
Check this: https://stackoverflow.com/a/54647682/305135

Stuck Loader in FutureBuilder

I'm new of Flutter and Dart in general, I'm trying to do a expansive computation during the loading of the page but the loader is stuck when I try to do something like this:
body: Center(
child:FutureBuilder(
future: _lorem()
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done){
print("loader");
return Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor)
);
}
[...]
Future<void> _lorem() async {
//there is not a request to service, there is a more than one filter on map and some lists. I set the for loop for example of a local computation
return Future(() {
for (int i = 0; i < 50000; i++){
print(i);
}
}
);
}
I think the easier way to implement this is using a field in your widget of type Completer, eg Completer calc. You can start your expensive computation in your widget initialization (never in your build function), and when the computation is done you complete that Completer by calling calc.complete().
In your widget's FutureBuilder you should then listen to calc's future by including future: calc.future instead of your future: _lorem().
See FutureBuilder for an example of this UI paradigm.
Solved with a Future and compute.
In the detail:
Future<List<CustomObject>> _retrieveCustomObjects() async {
SourceData data = SourceData(CustomSourceData());
return compute(getFilteredClients, data);
}
List<CustomObject> computeCustomObject(SourceData data) {
List<CustomObject> list = [];
// expensive logic on data, not only network call
return list;
}
class LoremIpsumClass {
// use where you need `List<CustomObject> value = await _retrieveFilteredClient();`
}

how to use values from async functions in dart

I want to use string value in my code but I am unable to do so. Please help, I am new to flutter.
// Database (db) is database sqlite
// Dog class has a String field name
Text func() async{
var dog = await db.firstDog();
return Text(dog.name);
}
The return type Text isn't a Text, as defined by the method func.dart(return_of_invalid_type).
use Future
ForExample
Future<Text> func() async{
String d = await getTest();
return Text(d);
}
Prerequisites
Do tell us where this function call is placed (eg. in repository or screen layer)
What you can do
Instead of returning the Text as Widget, you can just return a Future<String>
Future<String> func() async {
var dog = await db.firstDog();
return dog.name;
}
Assuming that you are utilizing this on your screen or widget class directly, you can do this
Database db = new Database();
String _dogName;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(_dogName),
),
);
}
void func() async {
setState(() async {
var dog = await db.firstDog();
_dogName = dog.name;
});
}
Check this article on medium. You can get a good understanding.
For asynchronous functions you should use Future class for returning the values.
Future<String> asyncFunc() async {
d = await db.firstDog();
return d.name;
}
Then use this string to set the text for a TextView

Unexpected behaviour of onChildAdded stream in firebase_database

I've recently started learning Flutter and the FlutterFire plugins. Yesterday I was working with the
firebase_database plugin which allows adding Firebase Realtime Database to Flutter. While trying out a
simple read on the database I noticed some strange behavior in one of the Streams provided by firebase_database which is onChildAdded.
So my problem is that when I use the onChildAdded stream with a StreamBuilder it only returns a single latest child. When I used to work with Firebase Database in Java the onChildAdded method was called for each child of a DatabaseReference instead of just the latest child. (Assuming that onChildAdded provides the same behavior in both Java and Dart)
I should also mention that when I use the onValue stream everything works fine and I get all the children of my DatabaseReference.
This is how my Firebase Database looks:
Code using onChildAdded
Widget _getBody(BuildContext context) {
final DatabaseReference databaseRef = FirebaseDatabase.instance.reference();
return StreamBuilder(
stream: databaseRef.child("notes").child('android').onChildAdded,
builder: (BuildContext context, AsyncSnapshot<Event> snapshot) {
if (snapshot.hasData) {
Map<dynamic, dynamic> notes = snapshot.data.snapshot.value;
notes.forEach(
(key, value) {
print(notes[key]);
}
);
}
return Container(); //Just a blank widget because builder has to return a widget
},
);
}
Output for onChildAdded:
Code using onValue
Widget _getBody(BuildContext context) {
final DatabaseReference databaseRef = FirebaseDatabase.instance.reference();
return StreamBuilder(
stream: databaseRef.child("notes").child('android').onValue,
builder: (BuildContext context, AsyncSnapshot<Event> snapshot) {
if (snapshot.hasData) {
List<dynamic> notes = snapshot.data.snapshot.value;
notes.forEach(
(item) {
print("$item \n");
}
);
}
return Container(); //Just a placeholder because builder has to return a widget
},
);
}
Output for onValue:
So I was hoping for a way where it's possible to get all the children using the onChildAdded stream. Any help is appreciated!

Categories

Resources