UPDATE 7/15/2021:
I found another similar question that helped me a bit:
Programmatically scrolling to the end of a ListView
As of now I am able to get it somewhat looking like what I want it to, but I am still getting an overflow issue.
Here is the updated Code
home.dart
class _HomePageState extends State<HomePage> {
// added a scroll controller to control my ListView
ScrollController scrollController = ScrollController();
void addEntry() {
setState(() {
entryTextController.text = '';
this.newEntry = !this.newEntry;
});
// on adding an item, I will scroll up in the ListView
Timer(
Duration(milliseconds: 100),
() =>
scrollController.jumpTo(scrollController.position.maxScrollExtent)
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(),
// instead of just the EntryList, I added a container
// with a fixed height. I'll probably change this later
Container(
height: 500.0,
child: EntriesList(props)
)
],
),
floatingActionButton: FloatingActionButton(onTap: addEntry)
);
}
}
EntryList.dart
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) {
return Entry();
}).toList();
myList.add(Visibility(
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
// This is the newest change. By adding a Placeholder, I am able to
// bring the screen up on the scrollController bringing this into
// position so that the user can then see the TextFormField
myList.add(Opacity(opacity: 0.0, child: Placeholder()))
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Async code here ...
return ListView(
children: getEntries(snapshot),
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
);
},
);
}
}
ORIGINAL:
I'm working on adding an entry to my ListView widget. Within my ListView Widget, I have embedded a hidden TextFieldInput that is shown once a user clicks the addEntry button.
My main issue is that once the list gets long enough, my list does not scroll up to allow the user to see their new entry that they are typing.
Here is my current code. In order to keep my question concise, I have removed some unrelated code.
main.dart
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(), // top widget here
// Scrollable list of dates
EntriesList(),
],
),
floatingActionButton: FloatingActionButton()
);
}
}
EntriesList.dart
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) { // getting my entry widgets here
return Entry();
}).toList();
myList.add(Visibility( // adding my hidden text input widget here
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return ListView( // I added several properties in hopes that it would
children: getEntries(snapshot), // work but to no avail
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
physics:
BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
);
},
);
}
}
Use the SingleChildScrollView widget. Can be used in HomePage or EntriesList by simply wrapping it with the SingleChildScrollView widget. Like this in the home page:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(), // top widget here
// Scrollable list of dates
SingleChildScrollView(child:widgetEntriesList()),
],
),
floatingActionButton: FloatingActionButton()
);
}
}
Or like this in the EntriesList:
class _EntriesListState extends State<EntriesList> {
List<Widget> getEntries(snapshot) {
myList = entryList.map<Widget>((entry) { // getting my entry widgets here
return Entry();
}).toList();
myList.add(Visibility( // adding my hidden text input widget here
visible: widget.newEntry,
child: Card(
child: ListTile(
title: TextFormField(),
))));
return myList;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _entryStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return SingleChildScrollView(
child:ListView(
children: getEntries(snapshot), // work but to no avail
padding: EdgeInsets.all(8.0),
shrinkWrap: true,
physics:BouncingScrollPhysics(),
)
);
},
);
}
}
Instead of wrapping ListView, you could instead wrap the StreamBuilder as well.
Related
I have this code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('All users'),
),
body: StreamBuilder<List<User>>(
stream: readUsers(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('error fetching data');
} else if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
// return const Text('no data to fect');
return Container(
padding: const EdgeInsets.all(10.0),
child: const Text('no data'),
);
} else {
final users = snapshot.data!;
return ListView(
children: users.map(buildUser).toList(),
);
}
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
}
Then at this point
return ListView(
children: users.map(buildUser).toList(),
);
I want to return data from another widget outside buildContext widget but the issue here is that I don't know how to pass the 'context' in the users.map(buildUser).toList() unorder to eliminate the error in the image below.
Create a class like bellow
import 'package:flutter/material.dart';
class GlobalContextService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
now assign this key to the MaterialApp in main.dart just like bellow
return MaterialApp(
navigatorKey: GlobalContextService.navigatorKey, // set property
);
Now you can access the context any where you want by using the following line of code
GlobalContextService.navigatorKey.currentContext
try this:
Widget buildUser(User user, BuildContext context) =>
Recommended approach ->User helper widget instead of helper method.
or
you can pass context as parameter to method
I'm trying to save the state of Listview.builder, while I am loading new data from the network.
In other words, I am trying to do lazy loader and using PageStorageKey in order to save the state of the Listview.builder and It works, partially.
So, problem is when I scroll right slowly, it works and saves current state. However If I scroll quickly, it brings me back at the first element.
Here is the code
class CategorySlider extends StatelessWidget {
const CategorySlider({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 260,
child: BlocBuilder<SliderCubit, SliderState>(
builder: (context, state) {
if (state is SliderLoading) {
return Center();
} else if (state is SliderLoaded) {
return NotificationListener<ScrollNotification>( // It is how I am listening Scrolls
onNotification: (notification) {
if (notification.metrics.extentAfter <= 0.0) {
BlocProvider.of<SliderCubit>(context).sliderLazyLoad(); // Here I am calling data from Network and switch states, while it's loading
return true;
}
return false;
},
child: ListView.builder(
key: const PageStorageKey<String>('Loaded'), //Here is PageStorageKey()
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: state.model.length,
itemBuilder: (context, index) {
return ListViewCenterFrame(
itemDistance: 25,
child: Stack(
children: [
// My widgets
],
),
index: index,
);
},
),
);
} else {
return const SizedBox();
}
},
),
);
}
}
here im try to use FutureBuilder for my list but I can't refresh by on pullRefresh
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refreshPhotos, // fatch snapshot.data!
child: FutureBuilder<String>(
future: userId as Future<String>,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return LayoutBuilder(builder: (context, constraints) {
return ListView(
scrollDirection: Axis.vertical,
children: [
AddBanners(userId: snapshot.data!), // future builder,it fatches data from api
DealsOfTheDay(userId: snapshot.data!), //future builder, , it fatches data from api
]);
});
} else {
return Center(child: JumpingText('Loading...'));
}
}),
);
I want fresh these widgets along with
refreshPhotos()
AddBanners(userId: snapshot.data!),
DealsOfTheDay(userId: snapshot.data!)
If you are looking for pull to refresh. Wrap your widgets with 'RefreshIndicator' widget on your desired screen.
Here is an example of my home screen which has pull to refresh.
#override
Widget build(BuildContext context) {
return Scaffold(
key: _con.scafoldKey,
body: WillPopScope(
onWillPop:() => DeviceUtils.instance.onWillPop(),
child: SafeArea(
child: Container(
color: ColorUtils.themeColor,
child: RefreshIndicator( //Just add this to your screen
color: ColorUtils.themeColor,
key: _con.refreshIndicatorKey,
strokeWidth: 4,
displacement: 80,
onRefresh: _refresh, //this is a function which you need to place under your home view state
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: Container(
color: Colors.white,
child: /// some more widgets
),
),
),
),
);
}
After adding the refresh indicator to your widgets, you need to add the _refresh function which will have all your api's that you want to reload.
Future<Null> _refresh() async{
//these two are my api's that i want to reload everytime an user pulls to refresh screen. You have to add your own apis here.
_con.getProfile(context);
_con.getUpcoming(context);
}
Voila. Now your user can reload data in the page and get the new state.
Hope this answers your question.
If the above is not what you want. You can use setState() inside your future builder. See the code below for example:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myData = _getData(); //<== (1) here is your Future
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _myData; //<== (2) here you provide the variable (as a future)
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return Column(
children: [
Icon(Icons.error),
Text('Failed to fetch data.'),
RaisedButton(
child: Text('RETRY'),
onPressed: (){
setState(){
_myData = _getData(); //<== (3) that will trigger the UI to rebuild an run the Future again
}
},
),
],
);
else
return createListView(context, snapshot);
}
},
);
return new Scaffold(
appBar: new AppBar(
title: new Text("Home Page"),
),
body: futureBuilder,
);
}
setState() will rebuild the widget with new values.
you can simply use in your main screen
setState((){});
it will rebuild all of the futureBuilder widgets in your screen and retrieve new data
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}');
}),
);
}
}
I have a Listview.builder() inside a FutureBuilder() that displays data fetched from API. I can retrieve the data successfully. But when I call the refreshData() function, previous data gets appended in the list.. How do I properly 'refresh' the widgets inside a FutureBuilder()?
Note: I'm only using get request here, so it's impossible that the data gets duplicated in the back-end. So the problem actually lies in displaying the data.
Here is my code:
class _MyHomePageState extends State<MyHomePage> {
List<Giver> _givers = [];
Future giversList;
getData() async {
_givers.addAll(await NetworkHelper().fetchGivers());
return _givers;
}
refreshData() {
giversList = getData();
}
#override
void initState() {
super.initState();
giversList = getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: (){
setState(() {
refreshData();
});
},
child: Text('Refresh'),
),
FutureBuilder(
future: giversList,
builder: (context, snapShot){
switch(snapShot.connectionState) {
case ConnectionState.none:
return Center(child: Text('none'));
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
//this is where the listview is created
case ConnectionState.done:
return ListView.builder(
shrinkWrap: true,
itemCount: _givers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapShot.data[index].name),
subtitle: Text(snapShot.data[index].address),
);
});
default:
return Center(child: Text('Default!'));
}
},
)
],
),
),
);
}
}
As #pskink mentioned in the comment above, I just replaced _givers.addAll(await NetworkHelper().fetchGivers()); with _givers = await NetworkHelper().fetchGivers();
Thanks for the help!