StreamBuilder stuck in connectionState.waiting in Flutter - flutter

I am trying to create a todo list application, i used Streambuilder to show list of Streams.
it is a simple application there is a button to add new task it is a floatingActionButton and a StreamController to manage the data, and have a TabBar with two tabs first and second so the StreamBuilder is in the first tab and other tab just contain a string in the center for now.
I can add tasks to the StreamController perfectly, but there is two issues:
1- when the program runs StreamBuilder stuck in ConnectionSatate.waiting if the Stream is null.
2- when i click second tab and came back to first tab the it also stuck in ConnectionSatate.waiting even my stream has data in it, and when i click add button to add new data it shows the data and the new one again.
here is my whole code:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final StreamController<List<String>> streamController =
StreamController<List<String>>.broadcast();
List<String> list = [];
#override
void initState() {
super.initState();
}
#override
void dispose() {
streamController.close();
super.dispose();
}
int i = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: PreferredSize(
preferredSize: Size.fromHeight(0),
child: Container(
height: 30,
width: MediaQuery.of(context).size.width,
child: TabBar(
labelColor: Theme.of(context).iconTheme.color,
indicatorColor: Colors.green.shade600,
tabs: [
Tab(text: 'first'),
Tab(text: 'second'),
]),
),
),
),
body: TabBarView(children: [
tasks(),
Center(
child: Text('SECOND TAB'),
)
]),
floatingActionButton: TextButton(
onPressed: () {
list.add('data ${++i}');
streamController.sink.add(list);
},
child: Icon(
Icons.add,
)),
),
);
}
Padding tasks() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 5),
child: Column(
children: [
Expanded(
child: StreamBuilder(
stream: streamController.stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) {
return const Center(
child: Text('There is no data'),
);
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
snapshot.data![index],
style: const TextStyle(
fontSize: 25,
),
),
));
})),
],
),
);
}
}
here is a video of my problem
I tried without StreamController but when i came back to first tab it shows the error that i can't listen to a Stream multiple times.

here is the answer, you need to use AutomaticKeepAliveClientMixin
I wrote a demo code sample for you https://dartpad.dartlang.org/?id=52e8e2ef8bad97f21dc91fa420dcec0e

Related

How can I use the variables of the Future<List> in the build? In this case it shows undefined name

Flutter Error
I tried a couple of ways but I thik so that I am missing a particular method or function here.
You may use FutureBuilder.
Since you did not include full code, you have to adapt this to your purpose
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.displayMedium!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: getRequest, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
),
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
children = const <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
),
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
},
),
);
}
}
There are 2 main points in your question:
How do I show Future values in Widgets? A/ You may use a FutureBuilder:
Widget that builds itself based on the latest snapshot of interaction
with a Future.
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateWidget, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
with the future being the method or the API request itself (I'm not using any architecture so you don't get lost)
and 2. Why am I getting the undefined name in "questions" variable? A/ That's because you're declaring that variable: List<Question> questions outside the method where you're calling it: "the build method", you could use a FutureBuilder as I said before or a StatefulWidget and calling an async method retrieving the data and updating the State of your list.

Screen goes blank if I search for something in TextField that is out of my list

I have created a list in another directory which is a list of JSON data and I have showed it on screen and also added a TextField which works as a search bar. it works fine if I search for a letter(or combination of letters) that is inside my list but when I search for something out of list the whole page goes blank except for the Appbar.
here's my code:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
// Hide the debug banner
debugShowCheckedModeBanner: false,
title: 'Kindacode.com',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List _items = [];
List _itemsForDisplay = [];
// Fetch content from the json file
Future<void> readJson() async {
final String response = await rootBundle.loadString('assets/Symptoms.json');
final data = await json.decode(response);
setState(() {
_items = data["Symptoms"];
_itemsForDisplay = _items;
});
}
#override
void initState() {
// TODO: implement initState
readJson();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
'Diagnose',
),
),
body: Padding(
padding: const EdgeInsets.all(25),
child: Column(
children: [
// Display the data loaded from sample.json
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return index == 0 ? _searchBar() : _ListItem(index);
},
itemCount: _itemsForDisplay.length,
),
)
],
),
),
);
}
_searchBar() {
return Padding(
padding: const EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(hintText: 'Search'),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_itemsForDisplay = _items.where((item) {
var itemTitle = item.toLowerCase();
return itemTitle.contains(text);
}).toList();
});
},
),
);
}
_ListItem(index) {
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
leading: Text(_itemsForDisplay[index]),
),
);
}
}
Here's a picture before for searching for something that is an entity of my list
and this happens when I search for "." or anything that is not in my list:
In your item builder return only _ListItem(index) and put _searchBar outside of Expanded. In current code, you are skipping first element from list and showing search bar instead, which is wrong. That's why when length of list is 0 you don't see anything. Because builder doesn't get executed.
So, your built method should look like this:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
'Diagnose',
),
),
body: Padding(
padding: const EdgeInsets.all(25),
child: Column(
children: [
// Display the data loaded from sample.json
_searchBar(),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return _ListItem(index);
},
itemCount: _itemsForDisplay.length,
),
)
],
),
),
);
}

How to make short ListView of DropDownButtons build faster?

I have a short ListView of a maximum of 10 items. Each list item will contain a DropDownButton which will hold around 1K DropDownMenuItems for selections.
In native Android, I was able to implement one that performed very smoothly, but with Flutter it takes a while to build the ListView which causes the UI to freeze.
In my case, I will need to rebuild the ListView upon every change in one of its items, so It will be a major issue.
Is there a way to make the ListView build faster, or at least be able to display a ProgressBar till it builds?
N.B: Using --profile configuration to simulate a release version improves the performance a lot, but still there is a sensed freeze.
Here's my sample code which you can directly copy/paste if you want to test it yourself.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool showList = false;
final List<DropdownMenuItem<int>> selections = List.generate(
1000,
(index) => DropdownMenuItem<int>(
value: index,
child: Text("$index"),
),
);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
width: double.infinity,
child: Column(
children: [
ElevatedButton(
child: Text("toggle list visibility"),
onPressed: () {
setState(() {
showList = !showList;
});
},
),
Expanded(
child: showList
? ListView.builder(
cacheExtent: 2000,
itemCount: 10,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Container(
height: 200,
color: Colors.green,
child: Column(
children: [
Text("List Item: $index"),
DropdownButton<int>(
onChanged: (i) {},
value: 1,
items: selections,
),
],
),
),
),
);
})
: Text("List Not Built"),
),
],
),
),
),
);
}
}
Load dropdown when clicking the button.
Add this widget on your main List View
InkWell(
onTap: () {
showDialog(
context: context,
builder: (_) {
return VendorListAlert(selectVendor: selectVendorTap);
});
},
child: // create a widget, looks like your drop down
),
Handle tap event
void selectVendorTap(pass your model){
// logic
}
Sample for custom Alert
No need to create a mutable widget, the immutable widget is better.
class VendorListAlert extends StatefulWidget {
final Function selectVendor;
const VendorListAlert({Key key, this.selectVendor}) : super(key: key);
#override
_VendorListAlertState createState() => _VendorListAlertState();
}
class _VendorListAlertState extends State<VendorListAlert> {
List<UserModel> _searchVendor = [];
#override
void initState() {
super.initState();
_searchVendor = List.from(ypModel);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
width: width,
child: ListView.builder(
shrinkWrap: true,
itemCount: _searchVendor.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {
widget.selectVendor(_searchVendor[index]);
Navigator.pop(context);
},
child:
),
);
},
),
),
);
}
}

Refresh the page data when you go to this page in the flutter

I'm trying to write a small application in which I collect data through api. I take the data, everything works. I decided to make a navigation bar to switch between pages. But when I try on the pages they are empty. In order for the data to be updated on the page, I need to click "Hot reload". I will be grateful for your help.
My main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_app_seals/model/dataArea_list/JsonDataArea.dart';
import 'package:flutter_app_seals/model/object_list/JsonObject.dart';
import 'package:flutter_app_seals/model/seals_list/JsonSeals.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Журнал пломби'),
),
// body: Seals(),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Seals List"),
trailing: Icon(Icons.arrow_back),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Seals()),
);
}
)
],
),
),
);
}
}
class Seals extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home:JsonParseSeals(),
);
}
}
My modul Seals:
import 'package:flutter/material.dart';
import 'package:flutter_app_seals/model/seals_list/SealsListGet.dart';
import 'package:flutter_app_seals/model/seals_list/ServicesSeals.dart';
class JsonParseSeals extends StatefulWidget {
//
JsonParseSeals() : super();
#override
_JsonParseSealsState createState() => _JsonParseSealsState();
}
class _JsonParseSealsState extends State <StatefulWidget> {
//
List<SealList> _seals;
bool _loading;
#override
void initState(){
super.initState();
_loading = true;
Services.getSeals().then((seals) {
_seals =seals;
_loading = false;
}
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Список пломби'),
),
body: ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(40),
itemCount: null == _seals ? 0 :_seals.length,
itemBuilder: (_,index) => Card(
color: Colors.red[300],
margin: EdgeInsets.symmetric(vertical: 7),
child:ListTile(
title: Text(_seals[index].sealNumber,
style: TextStyle(fontSize: 30),
),
subtitle: Text(
"${_seals[index].used}" ),
leading: Icon(Icons.local_activity,
size: 40,
color: Colors.black87,
),
),
),
),
);
}
}
My code :
Code after change:
Try to wrap your screen with data in FutureBuilder (you can read more about this widget here):
class _JsonParseSealsState extends State <StatefulWidget> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<SealList>>(
future: Services.getSeals(),
builder: (context, snapshot) {
// Data is loading, you should show progress indicator to a user
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// Data is loaded, handle it
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(40),
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
final item = snapshot.data[index];
return Card(
color: Colors.red[300],
margin: EdgeInsets.symmetric(vertical: 7),
child: ListTile(
title: Text(
item.sealNumber,
style: TextStyle(fontSize: 30),
),
subtitle: Text("${item.used}"),
leading: Icon(
Icons.local_activity,
size: 40,
color: Colors.black87,
),
),
);
},
),
}
);
}
}

Flutter :How to build a infinite list using future builder that display n-record at a time

I am trying to build a list view in flutter that load data base on index and record per page
I am able to display a fix number of record but need some help how get and display the next set of record and so on
Here is my code snippet
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: <Widget>[
searchBoxWidget(),
Expanded(
child: FutureBuilder(
future: getRecordToDisplay(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError) {
return Text('You have some error : ');
} else if (snapshot.data != null) {
return buildListView(snapshot);
} else {
return Text('You have some error : ');
}
}
},
),
),
],
));
}
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {}
});
}
Future<Jobs> getRecordToDisplay() async {
return await getJobs(startPage, recordPerFetch);
}
ListView buildListView(AsyncSnapshot snapshot) {
return ListView.builder(
itemCount: snapshot.data.hits.length,
controller: _scrollController,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
lobId: snapshot.data.hits[index].lobId,
atsReference: snapshot.data.hits[index].atsReference),
),
);
},
child: Container(
// width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Padding(
padding:
const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Text(
snapshot.data.hits[index].title,
style: TextStyle(
color: Color(0xff2175D9),
fontSize: 18.0,
),
),
),
),
Icon(
Icons.arrow_forward,
color: Colors.blue,
)
],
),
Text(
snapshot.data.hits[index].jobLocation.city +
" , " +
snapshot
.data.hits[index].jobLocation.stateAbbreviation,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 16.0,
),
),
SizedBox(
height: 8.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
snapshot.data.hits[index].salary.salaryString,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 16.0,
),
),
Text(
snapshot.data.hits[index].createdDate,
style: TextStyle(
color: Color(0xff0f1941),
fontSize: 14.0,
),
),
],
),
SizedBox(
height: 8.0,
),
Divider(color: Colors.brown),
],
),
));
});
}
So, it loads the first with n record but I don't know how to load the next set of pages when you reach the bottom of the current record with a future builder.
Thanks for your help
If you're expecting an infinite list to be displayed, won't StreamBuilder be better? Do you have a particular use case for the need to use FutureBuilder specifically? Here's a simple demo that uses Firestore to provide data, and ListView.builder with pagination.
This sample implements snippets from Firebase official doc for Firestore pagination.
There are two ways to load the data on the view in this demo.
Refresh the entire ListView using
RefreshIndicator
Scroll down to hit the bottom of the list to load up the next documents in the ListView.
ScrollController
is used to determine if the user has hit the bottom part of the list.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'DocObj.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var scrollController = ScrollController();
#override
void initState() {
super.initState();
getDocuments();
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels == 0)
print('ListView scroll at top');
else {
print('ListView scroll at bottom');
getDocumentsNext(); // Load next documents
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: listDocument.length != 0
? RefreshIndicator(
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
controller: scrollController,
itemCount: listDocument.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${listDocument[index].documentName}'),
);
},
),
onRefresh: getDocuments, // Refresh entire list
)
: CircularProgressIndicator(),
),
);
}
List<DocObj> listDocument;
QuerySnapshot collectionState;
// Fetch first 15 documents
Future<void> getDocuments() async {
listDocument = List();
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name")
.limit(15);
print('getDocuments');
fetchDocuments(collection);
}
// Fetch next 5 documents starting from the last document fetched earlier
Future<void> getDocumentsNext() async {
// Get the last visible document
var lastVisible = collectionState.docs[collectionState.docs.length-1];
print('listDocument legnth: ${collectionState.size} last: $lastVisible');
var collection = FirebaseFirestore.instance
.collection('sample_pagination')
.orderBy("name").startAfterDocument(lastVisible).limit(5);
fetchDocuments(collection);
}
fetchDocuments(Query collection){
collection.get().then((value) {
collectionState = value; // store collection state to set where to start next
value.docs.forEach((element) {
print('getDocuments ${element.data()}');
setState(() {
listDocument.add(DocObj(DocObj.setDocDetails(element.data())));
});
});
});
}
}
To parse the data inside the document, you can create a model for your object.
class DocObj {
var documentName;
DocObj(DocObj doc) {
this.documentName = doc.getDocName();
}
dynamic getDocName() => documentName;
DocObj.setDocDetails(Map<dynamic, dynamic> doc)
: documentName = doc['name'];
}
The sample handles this data from Firestore.
Here's how the app looks when running.