Animated List Multiple widgets used the same GlobalKey - flutter

I'm trying to use a GlobalKey for an AnimatedList, so I create an animation.
But regardless where I declare the Globalkey (Inside StatelessWidget, Global, static in class), I always get the duplicated Key error.
What am I doing wrong?
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: Scaffold(body: FirstRoute()),
));
}
class Keys {
static GlobalKey<AnimatedListState> favoriteDrink =
GlobalKey<AnimatedListState>(debugLabel: "TestKey");
}
class FirstRoute extends StatelessWidget {
final List<String> texte = [
"Hello",
"Hello1",
"Hello2",
"Hello3",
"Hello4",
"Hello5"
];
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: CustomListview(
texts: texte,
key: Keys.favoriteDrink,
)),
);
}
}
class CustomListview extends StatelessWidget {
final List<String> texts;
final GlobalKey<AnimatedListState> key;
CustomListview({this.texts, this.key});
#override
Widget build(BuildContext context) {
return AnimatedList(
key: key,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index, animation) {
return Text(
texts[index],
textAlign: TextAlign.center,
);
},
initialItemCount: texts.length,
);
}
}

I solved my question.
You can't name the custom key "key". This will lead to problems.
If you rename it to something like "customKey" it will work.
class CustomListview extends StatelessWidget {
final List<String> texts;
final GlobalKey<AnimatedListState> customKey; //<--- Change this
CustomListview({this.texts, this.customKey});
#override
Widget build(BuildContext context) {
return AnimatedList(
key: customKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index, animation) {
return Text(
texts[index],
textAlign: TextAlign.center,
);
},
initialItemCount: texts.length,
);
}
}

Related

Detect scrolling to end of a list with NotificationListener

I have a list view that show limited number of items. When the user scroll to the end I wanted to load next batch of items to the list.
I decided to use "NotificationListener" for this.
With the following code I was able to detect user reaching the end .
# #override
Widget build(BuildContext context) {
return Container(
height: 430,
child: NotificationListener<ScrollNotification>(
child: ListView.builder(
controller: controller,
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: widget.resList.length,
itemBuilder: (BuildContext ctx, int index) {
return GestureDetector(
onTap: null,
child: ReservationListTile(),
);
},
),
onNotification: (ScrollNotification notification) {
print(notification.metrics.pixels);
if (notification.metrics.atEdge) {
if (notification.metrics.pixels == 0) {
print('At left');
} else {
print('At right');
}
}
return true;
},
),
);
}
What I hoped was, when the user reach the end of the list she will swipe the list again and there is a trigger to detect that and I would be able to load the next set of items.
The problem I have is when the user reached the end, the edge event get triggered multiple times.
Is there a way to detect the user pulling the list back?
I had a very similar requirement, and I used a listener to detect when the user reached the end of the list. I actually decided not to wait until the user reaches the very end to provide a smoother user experience, so for example at 80% I already add the new items to the list.
See the following code. If you change _scrollThreshold to 0.75 for example, you will see the print statement executing. This is the place where you can add the new items.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => const MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
static const _itemCount = 32;
static const _scrollThreshold = 1.00;
late final ScrollController _scrollController;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
#override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => SizedBox(
height: 400,
child: Center(
child: ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: _itemCount,
itemBuilder: (BuildContext ctx, int index) => Card(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Center(child: Text('Item $index'))),
))));
void _scrollListener() {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * _scrollThreshold &&
!_scrollController.position.outOfRange) {
print('Scroll position is at ${_scrollThreshold * 100}%.');
}
}
}

Unable to scroll on widget even after wrapping column() with SingleChildScrollView()

Here is the root widget . The widget has a child BuyerPostList() which is a ListView type of widget. I removing the SingleChildScrollView() gives a render exception .After adding it the error no longer appears but the page is still not scrollable.
class PostsPage extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return StreamProvider<List<BuyerPost>>.value(
value: BuyerDatabaseService().buyerPosts,
child:Scaffold(
backgroundColor: Colors.white,
appBar: header(context,isAppTitle: true),
body:Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
BuyerPostList(),
SizedBox(height: 100,),
],
),
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewPost()),
);
},
label: Text(
'New',
style: TextStyle(fontWeight:FontWeight.w900,color:Color.fromRGBO(0, 0, 0, 0.4),),
),
icon: Icon(Icons.add,color:Color.fromRGBO(0, 0, 0, 0.4),),
backgroundColor:Colors.white,
),
),
);
}
}
Here is the List View BuyerPostList widget()
class BuyerPostList extends StatefulWidget {
#override
_BuyerPostListState createState() => _BuyerPostListState();
}
class _BuyerPostListState extends State<BuyerPostList> {
#override
Widget build(BuildContext context) {
final posts = Provider.of<List<BuyerPost>>(context) ?? [];
return ListView.builder(
shrinkWrap: true,
itemCount: posts.length,
itemBuilder: (context, index) {
return BuyerPostTile(post: posts[index]);
},
);
}
}
I hope i've been clear enough by my explanation. How will i make it scrollable?.
Thanks in advance.
Add physics: NeverScrollableScrollPhysics(), inside ListView.builder(
Code:
#override
Widget build(BuildContext context) {
final posts = Provider.of<List<BuyerPost>>(context) ?? [];
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: posts.length,
itemBuilder: (context, index) {
return BuyerPostTile(post: posts[index]);
},
);
}

Can I use 'index' in PageView widget in other widget?

I'm making an app divided to two sections: one(upper section) is PageView widget area, another(lower section) is Container widget area. I want the lower section to show 'we are in X page' when I change pages in the upper section.
I tried to use index of PageView widget in Container widget, but console said "undefined name 'index'".
So I declared like int index; as a global variable, and tried again, but it doesn't work. I think this index is different from index of PageView widget.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int index;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${index} page!'),
),
),
)
],
),
);
}
}
I'm a beginner of programming, and doing this as a hobby.
But I really like it. Actually I gave up my own study and career and stick to programming now. I hope you help me solve this problem.
Thank you. I love you.
yes. like controller.page for the current page.
class Sample extends StatelessWidget{
final int value;
Sample(this.value);
build(context) => Text("you are in $value");
}
and use Sample(controller.page)
EDIT: your code should be
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int currentPage=0;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
onPageChanged: (page){
setState(() {
currentPage=page;
});
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${currentPage} page!'),
),
),
)
],
),
);
}
}
Just add listener to PageController like that:
#override
void initState() {
super.initState();
index = 0;
controller.addListener(() {
setState(() {
index = controller.page.toInt();
});
});
}

How can fix ListView in column and add RefreshIndicator to the ListView

I have a ListView in column ,and need add RefreshIndicator to the ListView,but it not work well
I tried contain the listView by Expanded,then list display well,but when call the RefreshIndicator ,app dump...
some one can help me ,how to fix this code,thanks
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
return null;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[RefreshIndicator(child:
ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
), onRefresh: _handleRefresh,)
],
)
);
}
}
I have two remarks :
You should use the Expanded widget with the flex param.
You do not need to return anything in the setState method.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: RefreshIndicator(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
),
onRefresh: _handleRefresh,
),
)
],
),
);
}
}
The answer above( by Tarek Baz) is correct, however in some special cases (like deep/complicated widget tree) it might not be enough and you might have to pass the physics parameter to the ListView.builder() function.
ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: .... ... )

Access state from parent - flutter

I try to create a basic notes app to study about flutter and I do not quite understand how to notify my NotesContainer that the button has been pressed. I tried to create a ref to it but the adding function is in the state class that I'm not sure how to reach.
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final NotesContainer Notes = new NotesContainer();
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
Notes.add()
},
)
],
),
body: Notes
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return new _NotesContainer();
}
}
class _NotesContainer extends State<NotesContainer>{
final _notes = <NoteData>[new NoteData('title','thing to do'), new NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(new NoteData.noContent(title));
});
}
Widget build(BuildContext context){
return _buildNotesContainer();
}
_buildNotesContainer(){
return new ListView.separated(
itemCount: _notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(_notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
I guess the solution is somehow exposing the _function in the _NotesContainer via the stateful NotesContainer class. I wonder if there is a more elegant solution for this.
Thanks, Or
I think it makes more sense delegating the responsibility of adding a element further up in the widget tree. I modified your code to show how this works.
However, if you eventually get a deep widget tree and the children widgets require the _notes list, then I would recommend that you look into using a Inherited widget and add the _notes list to it, so you can access it without passing the state around too much.
import 'package:flutter/material.dart';
// Note the name change
class NotesPage extends StatefulWidget {
#override
_NotesPageState createState() => _NotesPageState();
}
class _NotesPageState extends State<NotesPage> {
final List<NoteData> _notes = <NoteData>[NoteData('title','thing to do'), NoteData('title2','thing to do2')];
void add({String title='1'}){ //just to test adding
setState(() {
_notes.add(NoteData.noContent(title));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('My Notes'),
backgroundColor: Color.fromRGBO(223, 175, 117, 1),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
add();
},
)
],
),
body: NotesContainer(notes: _notes)
);
}
}
class NoteData{
String title;
String content;
NoteData(this.title, this.content);
NoteData.noContent(t){
title = t;
content ='';
}
}
class NotesContainer extends StatelessWidget{
final List<NoteData> notes;
const NotesContainer({Key key, this.notes}) : super(key: key);
#override
Widget build(BuildContext context){
return ListView.separated(
itemCount: notes.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(notes[index].title),
);
},
padding: const EdgeInsets.all(10.0),
);
}
}
Hope it helps :-)