Avoid ListView's unwanted refresh - flutter

As the following animation displays, when I tap one of the list items that StreamBuilder() is querying, it shows the items data on the right darker container (it's always Instance of '_JsonQueryDocumentSnapshot'). But at the same time in each tap, the whole list is refreshing itself, which is not very cost-effective I believe.
How can I avoid this unwanted refresh?
Answers with GetX state management dependency are also welcome.
class Schedule extends StatefulWidget {
#override
_ScheduleState createState() => _ScheduleState();
}
class _ScheduleState extends State<Schedule> {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));
var _chosenData;
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _db.collection('Schedule').where('date', isGreaterThan: _yesterday).limit(10).orderBy('date').snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshot.data!.docs[index];
return ListTile(
leading: Icon(Icons.person),
title: Text(data['project'], style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(data['parkour']),
onTap: () {
setState(() {_chosenData = data;});
},
);
},
);
} else {
return Center(child: CupertinoActivityIndicator());
}
},
),
),
VerticalDivider(),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.black26,
child: Text('$_chosenData'),
),
),
],
);
}
}

To me the easiest solution would be just make it stateless and use a Getx class.
class ScheduleController extends GetxController {
var chosenData;
void updateChosenData(var data) {
chosenData = data;
update();
}
}
And your Schedule.dart would look like this:
class Schedule extends StatelessWidget {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));
#override
Widget build(BuildContext context) {
final controller = Get.put(ScheduleController());
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _db
.collection('Schedule')
.where('date', isGreaterThan: _yesterday)
.limit(10)
.orderBy('date')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshot.data!.docs[index];
return ListTile(
leading: Icon(Icons.person),
title: Text(data['project'],
style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(data['parkour']),
onTap: () => controller.updateChosenData(data), // calls method from GetX class
);
},
);
} else {
return Center(child: CupertinoActivityIndicator());
}
},
),
),
VerticalDivider(),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.black26,
child: GetBuilder<ScheduleController>(
builder: (controller) => Text('${controller.chosenData}'), // only this rebuilds
),
),
),
],
);
}
}
This way the listview.builder never rebuilds, only the Text widget directly inside the GetBuilder gets rebuilt when you selected a different ListTile.

Calling setState() notifies the framework that the state of Schedule has changed, which causes a rebuild of the widget and so your StreamBuilder.
You could move your stream logic to an upper level of the widget tree. So, setState() will not trigger a rebuild of StreamBuilder.
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Schedule')
.where(
'date',
isGreaterThan: DateTime.now().subtract(Duration(days: 1)),
)
.limit(10)
.orderBy('date')
.snapshots(),
builder: (context, snapshot) {
return Schedule(snapshot: snapshot); // Pass snapshot to Schedule
},
);
}
}
Another approach would be using Stream.listen in initState() which is called once. This way your stream won't be subscribed for each time setState() is called.
...
late StreamSubscription<QuerySnapshot> _subscription;
#override
void initState() {
_subscription = _db
.collection('Schedule')
.where('date', isGreaterThan: _yesterday)
.limit(10)
.orderBy('date')
.snapshots()
.listen((QuerySnapshot querySnapshot) {
setState(() {
_querySnapshot = querySnapshot;
});
});
super.didChangeDependencies();
}
#override
void dispose() {
_subscription.cancel(); // Cancel the subscription
super.dispose();
}
...

Related

Streambuilder did not display the data from firestore

i want to make a grid of image by 3 like instagram based on user upload. the streambuilder is not displaying any data from firestore.
i already separate the stream so its not inside the streambuilder.
this is the code
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
File? imageFile;
String? imageUrl;
final FirebaseAuth _auth = FirebaseAuth.instance;
String? myImage;
String? myName;
String buttonName = 'click';
int currentIndex = 0;
final icon = CupertinoIcons.chat_bubble_2;
final _constructed = FirebaseFirestore.instance
.collection('fotoupload')
.orderBy("createAt", descending: true)
.snapshots(); //construct stream first
//final Stream<QuerySnapshot> _constructed = FirebaseFirestore.instance
// .collection('fotoupload')
// .orderBy("createAt", descending: true)
// .snapshots();
void read_userInfo() async {
FirebaseAuth.instance.currentUser!;
FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then<dynamic>((DocumentSnapshot snapshot) async {
myImage = snapshot.get('userImage');
myName = snapshot.get('name');
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
read_userInfo(); // refresh and read the user info both myName and myImage
}
Widget gridViewWidget(String docId, String img, String userImg, String name,
DateTime date, String userId, int downloads) {
return GridView.count(
primary: false,
padding: EdgeInsets.all(5),
crossAxisSpacing: 1,
crossAxisCount: 1,
children: [
GestureDetector(
onTap: () {
//createOwnerDetails
},
child: Center(
child: Text(date.toString()),
),
),
GestureDetector(
onTap: () {
//createOwnerDetails
},
child: Image.network(
img,
fit: BoxFit.cover,
),
),
Center(child: Text(userId)),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: _constructed,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data!.docs.isNotEmpty) {
return GridView.builder(
itemCount: snapshot.data!.docs.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return gridViewWidget(
snapshot.data!.docs[index].id,
snapshot.data!.docs[index]['Image'],
snapshot.data!.docs[index]['userImage'],
snapshot.data!.docs[index]['name'],
snapshot.data!.docs[index]['createAt '].toDate(),
snapshot.data!.docs[index]['uid'],
snapshot.data!.docs[index]['downloads'],
);
},
);
} else {
return Center(
child: Text(
'There is no tasks',
style: TextStyle(fontSize: 20),
),
);
}
}
return Center(
child: Text(
'Something went wrong',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
);
},
),
);
}
}
does anyone have suggestion? the streambuilder is not displaying the data. and its shows "There is no task".
You are reconstructing the stream again after every build, I recommend you to construct the stream in a private variable then use it in the stream in place of
FirebaseFirestore.instance.collection('fotoupload').orderBy("createAt", descending: true).snapshots()
as documented here It is implemented like this
class _HomePageState extends State<HomePage> {
final _constrcted = FirebaseFirestore.instance
.collection('fotoupload')
.orderBy("createAt", descending: true)
.snapshots();
//....
#override
Widget build(BuildContext context) {
return Scaffold(body: StreamBuilder<QuerySnapshot>(
stream: _constrcted,
builder: (context, snapshot) {
//...
}
stream: FirebaseFirestore.instance
.collection('fotoupload')
.orderBy("createAt", descending: true)
.snapshots(),
There's your prooblem. Do not create the stream in the stream: parameter of a StreamBuilder. It will get re-created on each call to build(), up to 60 times per second if there's an animation on the page. This doesn't give time for any data to appear... in fact it throws it away on each rebuild.
Follow the advice from the FutureBuilder and StreamBuilder documentation: create and initialize the stream in the state and initState of your widget. The value must remain constant for StreamBuilder to do its job.
I have more details and a demonstration at https://youtu.be/sqE-J8YJnpg.

Getting data from a document in FireBase and adding them into a map

I am trying to transfer my data from FireBase Collection to a map. Then, the map data will go into MultiSelectBottomSheetField.
Problems: I am getting "Instance of '_JsonQueryDocumentSnapshot'" instead of 'Home' for example.
I still not getting the list of context from collection into MultiSelectItem
Also, I have noticed that when I start the app, the MultiSelectionItem is empty. It is displaying "Instance of '_JsonQueryDocumentSnapshot'" when I display the main view a second time. I guess that a SetState is probably missing somewhere. But as I can not write set state in a constructor, I am puzzled.
This is driving me nuts as I do not find where the problem is coming from.
Many thanks for your help.
import 'package:flutter_swipe_action_cell/core/cell.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:multi_select_flutter/bottom_sheet/multi_select_bottom_sheet_field.dart';
import 'package:multi_select_flutter/util/multi_select_item.dart';
String inboxTaskDisplayed='';
int nbRecord=0;
var taskSelectedID;
var taskDone;
//------TEST
class ContextExisting {
final int id;
final String name;
ContextExisting({
this.id,
this.name,
});
}
List<ContextExisting> _contexts = [];
List <ContextExisting>allMyContext=[];
TextEditingController passController = new TextEditingController();
//-----------------
var documentID;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
//_-----------------
class Inbox_Test_For_Chip_Trial extends StatefulWidget {
final String screenSelected;
final String titlePage;
Inbox_Test_For_Chip_Trial(Key key, {#required this.screenSelected, #required this.titlePage,}) : super(key: key);
#override
_Inbox_Test_For_Chip_TrialState createState() => _Inbox_Test_For_Chip_TrialState(screenSelected, titlePage);
}
class _Inbox_Test_For_Chip_TrialState extends State<Inbox_Test_For_Chip_Trial> {
GlobalKey<FormState> _inboxFormKey = GlobalKey<FormState>();
String screenSelected;
String titlePage;
_Inbox_Test_For_Chip_TrialState(this.screenSelected, this.titlePage,);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text(titlePage + ' ('+nbRecord.toString()+')'),
actions: <Widget>[
],
),
backgroundColor: Colors.white,
body: Container(
height: 250,
child: Column(
//mainAxisAlignment: MainAxisAlignment.center,
children: [
//FOR CONTEXT
Flexible(child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('contexts')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
else {
var docs = snapshot.data.docs;
print('docs');
print (docs[2].id);
print(snapshot.data.docs.length);
int nbContext = snapshot.data.docs.length;
for (int i=0;i<nbContext; i++) {
_contexts.addAll([ContextExisting (id:i, name:snapshot.data.toString())]);
print(_contexts);
}
return Container(
height: MediaQuery.of(context).size.height * .78,
width: MediaQuery.of(context).size.width,
child: ListView(
children: snapshot.data.docs.map((document) {
return Wrap(
children: [Card(
child: SwipeActionCell(
key: ObjectKey(document['context_Name']),
trailingActions: <SwipeAction>[
],
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.keyboard_arrow_right),
onPressed: () async {
taskSelectedID = FirebaseFirestore
.instance
.collection('Users')
.doc(
FirebaseAuth.instance.currentUser
.uid)
.collection('contexts')
.doc(document.id).toString();
}
),
leading: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 30,
minHeight: 35,
maxWidth: 30,
maxHeight: 35,
),
//InkWell(child: Icon(Icons.check_box_outline_blank),
),
title: Text(
document['context_Name'],
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
),
]
);
}).toList(),
),
);
}
}),),
Column(children:[
TestWidgetContext(),
]), //MyHomePage())
],
),
),
//bottomNavigationBar: MyBottomAppBar(), //PersistentBottomNavBar(),
);
}
class TestWidgetContext extends StatefulWidget {
TestWidgetContext({Key key}) : super(key: key);
#override
_TestWidgetContextState createState() => _TestWidgetContextState();
}
class _TestWidgetContextState extends State<TestWidgetContext> {
List itemsContext;
List<ContextExisting> _selectedContext5 = [];
final _itemsContext = _contexts
.map((context) => MultiSelectItem<ContextExisting>(context, context.name))
.toList();
#override
void initState() {
_selectedContext5 = _contexts;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
MultiSelectBottomSheetField(
buttonText: Text("Contexts"),
onConfirm: (val2) {
},
items: _itemsContext,
// initialValue:
// _itemsContext,
),
],
);
}
}
Since the code has a lot of errors, I am writing down my findings.
allMyContext is a list and if you wish to add elements to it you should be wring it as
allMyContext.add(ContextExisting (id:i, name:doc.name));
instead of List<allMyContext> = ContextExisting (id:i, name:doc.name)
Insde the on pressed
taskSelectedID = (document.id).toString();
since you already have taken a document you don't have to query it again
final doc = FirebaseFirestore.instance.collection('users').doc('contexts').get();
and
FirebaseFirestore.instance.collection('Users').doc(FirebaseAuth.instance.currentUser.uid).collection('contexts').snapshots(),
These two are contradictory. Is contexts a collection or a document?
Problem solved.
child: Column(
//mainAxisAlignment: MainAxisAlignment.center,
children: [
//FOR CONTEXT
Flexible(child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('contexts')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
} else if (snapshot.hasData) {
// if connectionState is waiting
if (snapshot.connectionState == ConnectionState.waiting)
{
return Center(child: CircularProgressIndicator());
} else {
for (int i=0;i<snapshot.data.docs.length;i++){
DocumentSnapshot snap = snapshot.data.docs[i];
_contexts.add(snap['context_Name']);
}
}
}
// return widgets and use data
return Column(children:[
TestWidgetContext(),
]); //MyHomePage())

Cannot display data downloaded from Firestore using Listview.Builder and ListTile

I want to display a list from downloading the data from firestore. The download is successful (the full list can be printed) but somehow it cannot be displayed. Simply nothing is shown when I use the ListView.builder and ListTile. Pls help what is the problem of my code. Great thanks.
class DownloadDataScreen extends StatefulWidget {
#override
List<DocumentSnapshot> carparkList = []; //List for storing carparks
_DownloadDataScreen createState() => _DownloadDataScreen();
}
class _DownloadDataScreen extends State<DownloadDataScreen> {
void initState() {
super.initState();
readFromFirebase();
}
void readFromFirebase() async {
await FirebaseFirestore.instance
.collection('carpark')
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot cp) {
widget.carparkList.add(cp);
//to prove data are successfully downloaded
print('printing cp');
print(cp.data());
print(cp.get('name'));
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
'Car Park',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: SafeArea(
child: Column(
children: [
Expanded(
flex: 9,
child: Container(
child: ListView.builder(
itemCount: widget.carparkList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.carparkList[index].get('name')),
subtitle: Text(
widget.carparkList[index].get('district')),
onTap: () {
},
);
},
),
),
),
],
),
),
);
}
}
create the list in state add it to the top line of the initState method List carparkList = [];
class DownloadDataScreen extends StatefulWidget {
_DownloadDataScreen createState() => _DownloadDataScreen();
}
class _DownloadDataScreen extends State<DownloadDataScreen> {
List<DocumentSnapshot> carparkList = []; //List for storing carparks
void initState() {
super.initState();
readFromFirebase();
}
void readFromFirebase() async {
await FirebaseFirestore.instance
.collection('carpark')
.get()
.then((QuerySnapshot snapshot) {
snapshot.docs.forEach((DocumentSnapshot cp) {
widget.carparkList.add(cp);
//to prove data are successfully downloaded
print('printing cp');
print(cp.data());
print(cp.get('name'));
});
});
}

Flutter How to Populate ListView on app launch with sqflite?

I'm trying to display data in a ListView with a FutureBuilder. In debug mode, when I launch the app, no data is displayed, but, if I reload the app (hot Reload or hot Restart), the ListView displays all the data. I already tried several approaches to solve this - even without a FutureBuilder, I still haven't succeeded. If I create a button to populate the ListView, with the same method "_getregistos()", the ListView returns the data correctly.
This is the code I'm using:
import 'package:flutter/material.dart';
import 'package:xxxxx/models/task_model.dart';
import 'package:xxxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
dynamic tasks;
final textController = TextEditingController();
_getRegistos() async {
List<TaskModel> taskList = await _todoHelper.getAllTask();
// print('DADOS DA tasklist: ${taskList.length}');
return taskList;
}
TaskModel currentTask;
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getRegistos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
tasks = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: tasks == null ? 0 : tasks.length,
itemBuilder: (BuildContext context, int index) {
TaskModel t = tasks[index];
return Card(
child: Row(
children: <Widget>[
Text('id: ${t.id}'),
Text('name: ${t.name}'),
IconButton(
icon: Icon(Icons.delete), onPressed: () {})
],
),
);
},
);
}
return Loading();
}),
],
),
),
);
}
}
Thank you.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder returns ListView widget without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return _buildErrorWidget();
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);
Thanks for your help.
I already implemented ConnectionState in the FutureBuilder and the issue persists.
When I launch the app, I get error "ERROR or No-Data" (is the message I defined in case of error of no-data.
If I click on the FlatButton to call the method "_getTasks()", the same method used in FutureBuilder, everything is ok. The method return data correctly.
This is the code refactored:
import 'package:flutter/material.dart';
import 'package:xxxx/models/task_model.dart';
import 'package:xxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final textController = TextEditingController();
Future<List<TaskModel>> _getTasks() async {
List<TaskModel> tasks = await _todoHelper.getAllTask();
print('Tasks data: ${tasks.length}');
return tasks;
}
TaskModel currentTask;
//list to test with the FlatButton List all tasks
List<TaskModel> tasksList = [];
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//when clicking on this flatButton, I can populate the taskList
FlatButton(
child: Text('Show all Tasks'),
onPressed: () async {
List<TaskModel> list = await _getTasks();
setState(() {
tasksList = list;
print(
'TaskList loaded by "flatButton" has ${tasksList.length} rows');
});
},
color: Colors.red,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getTasks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return Text('ERROR or NO-DATA');
}
// return data widget
return ListItems(context, snapshot.data);
// return loading widget while connection state is active
} else
return Loading();
},
),
],
),
),
);
}
}
//*****************************************
class ListItems extends StatelessWidget {
final List<TaskModel> snapshot;
final BuildContext context;
ListItems(this.context, this.snapshot);
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: snapshot == null ? 0 : snapshot.length,
itemBuilder: (context, index) {
TaskModel t = snapshot[index];
return Text(' ${t.id} - ${t.name}');
}),
);
}
}

Flutter - StreamBuilder - Refresh

I have a StreamBuilder inside my Widget build of UserListDart:
StreamBuilder(
stream: stream.asStream(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasData) {
return Expanded(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
snapshot.data[index].firstname + " " +
snapshot.data[index].lastname
),
onTap: () {
Navigator.of(context).push(DetailScreenDart(snapshot.data[index]));
},
);
}
)
);
}
}
...
)
The Stream is defined in the initState:
Future<List> stream;
#override
void initState() {
super.initState();
stream = fetchPost();
}
The fetchPost() is an api call:
Future<List<User>> fetchPost() async {
final response = await http.get('url');
final jsonResponse = json.decode(response.body);
List<User> users = [];
for(var u in jsonResponse){
User user = User(
firstname: u["firstname"],
lastname: u["lastname"],
);
users.add(user);
}
return users;
}
I Navigate to another Page to change for example the firstname (api get updated) and I Navigate back to the UserList:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
fetchPost();
});
But the StreamBuilder won't get updated and I don't know why.
Note:
I think the StreamBuilder don't realise that a change has happend when I navigate back. It only applies the changes if I reopen the Page..
You should be using setState and updating your stream variable with the result of the fetchList() call:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
setState((){
stream = fetchPost();
});
});
Here's a working example of what you want to achieve:
class StreamBuilderIssue extends StatefulWidget {
#override
_StreamBuilderIssueState createState() => _StreamBuilderIssueState();
}
class _StreamBuilderIssueState extends State<StreamBuilderIssue> {
Future<List<String>> futureList;
List<String> itemList = [
'item 1',
'item 1',
'item 1',
'item 1',
'item 1',
];
#override
void initState() {
futureList = fetchList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Center(
child: StreamBuilder(
stream: futureList.asStream(),
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return Text(snapshot.data[index]);
},
);
}else{
return CircularProgressIndicator();
}
},
),
),
),
RaisedButton(
onPressed: goToAnotherView,
child: Text('Next View'),
),
RaisedButton(
onPressed: addItem,
child: Text('AddItem'),
)
],
),
);
}
Future<List<String>> fetchList(){
return Future.delayed(Duration(seconds: 2), (){
return itemList;
});
}
void goToAnotherView(){
Navigator.push(context, MaterialPageRoute(
builder: (context){
return StreamBuilderIssueNewView(addItem);
})
).then((res){
setState(() {
futureList = fetchList();
});
});
}
void addItem(){
itemList.add('anotherItem');
}
}
class StreamBuilderIssueNewView extends StatelessWidget {
final Function buttonAction;
StreamBuilderIssueNewView(this.buttonAction);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
Text('New view'),
RaisedButton(
onPressed: buttonAction,
child: Text('AddItem'),
)
],
),
),
);
}
}
By the way, you could also just use a FutureBuilder as your are not using a real Stream here, just an api fetch and you have to update with setState anyway.