I am fetching ListView.builder() items from Cloud Firestore inside a StreamBuilder. I want the new item to be added at the top of the ListView. How can I do that? I tried reverse : true , though it reverse the ListView, but when there is only 2/3 items, the ListView looks ugly, as the ListView starts from bottom and the upper portion of the screen remains empty.
Added shrinkWrap: true and put the ListView inside an Align widget with alignment: Alignment.topCenter and got the result I wanted!
Align(
alignment: Alignment.topCenter,
child: ListView.builder(
reverse: true,
shrinkWrap: true,
...
...
)
)
if you want to add new item at the top of the the listview and if you are
using firestore used Timestamp
firebase. firestore. Timestamp
A Timestamp represents a point in time independent of any time zone
use this method
Timestamp.now()
fetch data from firestore order by time using StreamBuilder
Widget item() =>
StreamBuilder<QuerySnapshot>(
//fetch data from friends collection order by time
stream: Firestore.instance.collection("friends").orderBy(
"time", descending: true).snapshots(),
builder: (context, snapshot) {
//if data not exist show loading indicator
if (!snapshot.hasData)
return CircularProgressIndicator();
//if data exist
return ListView.builder(itemBuilder: (context, index) {
return Text(snapshot.data.documents[index].data['name']);
});
},
);
add data in firestore
Firestore.collection("friends")
.document(friends.otherUID)
.setData({"name" : "xyz","time": Timestamp.now()});
if you want to update item and you want to add updated item at the top of the listview
update data
Future<void> updatefriends({String name,Timestamp time}) async {
Map<String, Object> updateFriend = Map();
if (name.isNotEmpty) updateFriend['name'] = name;
if (time !=null) updateFriend['time']=time;
Firestore.instance.collection("friends")
.document(uid)
.updateData(updateFriend);
}
Just create a variable with the reverse index of the snapshots lenght, here is an example:
StreamBuilder(
stream: Firestore.instance.collection('news').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) return Text('Loading...');
int reverseIndex = snapshot.data.documents.length;
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
reverseIndex -=1;
return _buildItems(context, snapshot.data.documents[reverseIndex]);
});
}),
Keep in mind what comes first in your list, ie at the top, are the items that are in the first positions of your array. If the case, you can invert the array.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
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> {
int _counter = 0;
List<int> Items = [5, 4, 3, 2, 1];
void _incrementItems() {
setState(() {
Items = List.from([9, 8, 7, 6])..addAll(Items);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: ListView.builder(
itemCount: Items.length,
itemBuilder: (context, index) {
return Text(
"Item "+Items[index].toString());
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementItems,
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
StoreConnector<_ViewModel, List<Message>>(
converter: (store) {
// check mark ,reverse data list
if (isReverse) return store.state.dialogList;
return store.state.dialogList.reversed.toList();
},
builder: (context, dialogs) {
// Add a callback when UI render after. then change it direction;
WidgetsBinding.instance.addPostFrameCallback((t) {
// check it's items could be scroll
bool newMark = _listViewController.position.maxScrollExtent > 0;
if (isReverse != newMark) { // need
isReverse = newMark; // rebuild listview
setState(() {});
}
});
return ListView.builder(
reverse: isReverse, // if it has less data, it will false now;
controller: _listViewController,
itemBuilder: (context, index) => _bubbleItem(context, dialogs[index], index),
itemCount: dialogs.length,
);
},
)
Related
So, all I'm trying to do is create a StreamBuilder that listens to the "raids" collection on Firebase, and return a widget for each document using a ListView.builder (though I'm not entirely sure this is the right way to go about this, I'm pretty new).
From everything I've seen, my code should be working properly but obviously I've misunderstood something along the way.
I've already confirmed that the field I'm trying to pass into my Text widget is accurate and that there is data within the snapshots, what do I do next?
class HostedRaids extends StatefulWidget {
const HostedRaids({Key? key}) : super(key: key);
#override
State<HostedRaids> createState() => _HostedRaidsState();
}
class _HostedRaidsState extends State<HostedRaids> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: (FirebaseFirestore.instance.collection('raids').snapshots()),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
var raidSnapshot = snapshot.data!.docs[index];
return Row(
children: [
Text(
raidSnapshot['creatorID'],
style: const TextStyle(color: Colors.white),
),
],
);
},
);
} else {
throw ('error');
}
});
}
}
Am a completely new flutter dev. I am trying to save a document from a queried firestore list on another saved documents page like an add to cart functionality. Am passing doc id as arguments to another page from firestore so that I get data based on the previous selection. Now how can I send the firestore reference and save it to the other screen without navigating to it so that users are able to save their favorite docs on another page and access them? Here is my Assignment page that lists the docs based on the previous selection.
class Assignments extends StatelessWidget {
final String programId;
final String yearId;
final String semesterId;
final String courseId;
const Assignments(
{Key key, this.programId, this.yearId, this.semesterId,
this.courseId})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar2(title: 'Assigment'.toUpperCase(), ),
body: Column(
children: [
Expanded(
child: ContentArea(
addPadding: false,
child: StreamBuilder(
stream:
getAssignment(programId, yearId, semesterId, courseId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
color: kOnSurfaceTextColorYellow),
);
}
return ListView.separated(
padding: UIParameters.screenPadding,
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
final data = snapshot.data.docs[index];
return DisplayCard(
title: data['nameOfAssignment'],
icon: Icons.add,
// Here is the action i want that should save the documment to
// the SavedPage empty list without navigating to it
onTapIconSave: (){}
onTap: () => Get.to(Pdf(
nameOfAssignment: data['nameOfAssignment'],
pdfUrl: data['pdfUrl'],
)),
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 10,
);
},
);
})),
),
],
),
);
}
}
Here is the SavedPage which may be similar to the cart page. Am not sure what to do in order to save the Document from the Assignment Page in a Dynamic growable list
class Saved extends StatefulWidget {
const Saved({ Key key }) : super(key: key);
#override
State<Saved> createState() => _SavedState();
}
class _SavedState extends State<Saved> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar2(title: 'Saved'),
body: Column(
children: [],
),
);
}
}
You can add a state management package like Provider or Bloc, also you could save your data in your local database and access them from there. I recommend Provider, easy to use, and its what you need.
i have ListView.builder that display data from server in stream from older to latest date dates . that's mean New data always comes to the top of the ListView.builder and while ListView.builder receiving new item it resize its self so the scroll move go up step by step. how to prevent that ? ... ok i have image here
i need number 5 to happen.
of course i can make the property reverse to true . ok this will solve one side only and the same Scenario will happen from down the page
ListView.builder(
itemBuilder: myList.length,
itemBuilder: (BuildContext context, int index) {
return myList['item']
},
),
any help guys most welcome . thanks
First of all, some background on why the behavior you want to avoid happens:
Flutter does not remember to which item the user has scrolled. Instead, it stores how far the user already scrolled, thus when rebuilding, the old scroll offset will correspond to a different item as some items have been inserted in the list prior to the one visible at a specific scroll offset. Here's a possible fix: You can give the ListView.builder a ScrollController. Using the ScrollController you can increase the current scroll offset by the amount of space, the new, at the top of the list inserted items occupy, like so: controller.jumpTo(controller.offset + tileHeight*(newLength - _lastLength));
Here is a small code example in which I implemented it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
final tileHeight = 50.0;
int _lastLength = -1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<String>>(
stream: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final newLength = snapshot.data!.length;
if(newLength > _lastLength && _lastLength > 0){
controller.jumpTo(controller.offset + tileHeight*(newLength - _lastLength));
}
_lastLength = newLength;
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return SizedBox(
height: tileHeight,
child: ListTile(
title: Text(
snapshot.data![index],
),
),
);
},
controller: controller,
);
}
return const CircularProgressIndicator();
},
),
);
}
Stream<List<String>> getData() async* {
yield List.generate(20, (index) => index.toString());
await Future.delayed(const Duration(seconds: 5));
yield ["-1"] + List.generate(20, (index) => index.toString());
}
}
I started studying flutter and I'm having a doubt about LsitViewBuilder.
I have this ListView that accesses the JSON data locally by rootBundle, but I would like that when I click on some item it would only open it on the second page.
I wanted so much to know how you can select.
My ListView
List<dynamic> buy;
#override
void initState() {
super.initState();
rootBundle.loadString('assets/dados.json').then((jsonData) {
this.setState(() {
buy = jsonDecode(jsonData);
});
});
}
........
ListView.builder(
itemCount: buy?.length ?? 0,
itemBuilder: (_, index) {
return buildCardBuy(context, index, buy);
}
),
You can wrap your list view item with the GestureDetector widget, making the Tap event to navigate to another page with the item tapped.
ListView.builder(
itemCount: buy?.length ?? 0,
itemBuilder: (_, index) {
return GestureDetector(
child: buildCardBuy(context, index, buy),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
// the first refeers to the property on your detail DetailScreen
// and the second refers to the current buy being render on
// this list view builder
builder: (context) => DetailScreen(buy: buy),
),
);
);
}
),
And in your DetailScreen something like
class DetailScreen extends StatelessWidget {
final dynamic buy;
DetailScreen({Key key, #required this.buy}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
),
body: Container()
);
}
}
I would build a second widget (let's call it ItemWidget) which represents the detail page for the object you want to 'open'.
Then I would add to that ItemWidget a property for the object data that you need to pass.
After that I would implement the logic so that when the list item is clicked, it switches the current list widget with a new ItemWidget and passes to it the properties of the clicked object.
I'm using photo_view_gallery class and I would like to show dynamically the value of current image index of PhotoViewGallery.builder into the appBar.
I'm new in Flutter, I googled a lot but I can't found any solution for this
body: PhotoViewGallery.builder(
itemCount: listaPagine.length,
builder: (context, index) {
saveIndex(index);
String myImg =
'http://www.attilofficina.altervista.org/phpbackend/JOB/000004/fullsize/' +
listaPagine[index].nomefile.toString() +
'.jpg';
return PhotoViewGalleryPageOptions(
imageProvider: AdvancedNetworkImage(myImg,
retryLimit: 1, timeoutDuration: Duration(seconds: 30)),
);
},
),
I also try a function that save index to another variable, but the value is still unavailable in appBar
Here the code and position of this function (in appBar is shown null)
class GalleryPageState extends State<GalleryPage> {
int curr;
...
#override
Widget build(BuildContext context) {
...
saveIndex(int index) {
int curr = index;
print('*** curr = ' + curr.toString()); /// PRINTS CORRECT VALUE
return curr;
}
...
return Scaffold(
appBar: AppBar(
title: Text(curr.toString(), /// BUT IT SHOWS NULL
),
),
body: PhotoViewGallery.builder(
itemCount: listaPagine.length,
builder: (context, index) {
salvaIndex(index);
String myImg =
'http://www.attilofficina.altervista.org/phpbackend/JOB/000004/fullsize/' +
listaPagine[index].nomefile.toString() +
'.jpg';
return PhotoViewGalleryPageOptions(
imageProvider: AdvancedNetworkImage(myImg,
retryLimit: 1, timeoutDuration: Duration(seconds: 30)),
);
},
),
);
}
}
Can someone help me?
special thanks mario
I think you can use onPageChanged
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
final List<String> listaPagine = [
'https://picsum.photos/id/451/200/300',
'https://picsum.photos/id/200/200/300'
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('# $_currentIndex'),
),
body: PhotoViewGallery.builder(
itemCount: listaPagine.length,
builder: (BuildContext context, int index) {
String myImg = listaPagine[index];
return PhotoViewGalleryPageOptions(
imageProvider: NetworkImage(myImg),
);
},
onPageChanged: (int index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
There's three things wrong in your following code:
saveIndex(int index) {
int curr = index;
print('*** curr = ' + curr.toString()); /// PRINTS CORRECT VALUE
return curr;
}
Here you are creating new int curr variable which should be curr to use the existing global scope variable.
You should create the saveIndex method outside of build function.
Also you should update the variable in setState like
setState((){
curr=index;
});
This will update the variable and recreate the widget tree with AppBar with updated values.
Also you don't need the return statement in saveIndex
Editing for #attila's help
The basic about the state is, it just keep the current state or values in memory. You can always change any value & it will be updated in the state but it will not reflected on the UI. Because your application or view still don't know that some value is updated & it needs to re-render the UI.
That's where the setState come in picture. What it does is while updating the value it also call the build function so new UI with updated values could be re-rendered on screen.
To test it, you can just update the values & call setState((){}); after that. It will still work as same.
Hope this helps you.