filtering Streambuilder/ listviewBuilder flutter - flutter

i am new to flutter and been trying to create a function that refresh the ListView.builder based on users choice.i am saving cities names as Strings inside my firestore documents in user collection.
i have multiple buttons that presents different cities and based on choice i need the ListView builder to rebuild. i have been struggling for a while trying to find the solution to this.
anyone here can help?
this is how i retrieve data from firestore
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('loading...');
return Container(
width: 890.0,
height: 320.0,
margin: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 00.0),
child: new ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
User user = User.fromDoc(snapshot.data
.documents[index]);
return Padding(
padding: const EdgeInsets.only(top: 0),
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(0),
),
child: _buildCard(user)),
);
}),
);
},
),

I just wrote this code to show the implementation for static no of cities, clicking the buttons changes the index which then changes the texts(you will change them to stream builders with custom city streams), you can also scale it to dynamic list by manipulating the city list.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key,}) : super(key: key);
​
​
#override
_MyHomePageState createState() => _MyHomePageState();
}
​
class _MyHomePageState extends State<MyHomePage> {
int stackIndex = 0;
​
final List<String> cities = ['Berlin', 'Denver', 'Nairobi', 'Tokyo', 'Rio'];
​
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children : [
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children : cities.map((city){
return RaisedButton(
child : Text(city),
onPressed : (){
setState((){
this.stackIndex = cities.indexOf(city);
});
}
);
}).toList()
),
IndexedStack(
index : stackIndex,
children: cities.map((city){
return yourStreamBuilder(city);
}).toList()
),
])
),
);
}
Widget yourStreamBuilder(String city){
//you can use your custom stream here
//Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
​
​
return Text(city);//replace this with your streamBuilder
}
}
​

int stackIndex = 0;
final List<String> cities =[
'Stockholm',
'Malmö',
'Uppsala',
'Västerås',
'Örebro',
'Linköping',
'Helsingborg',
'Jönköping',
'Norrköping',
'Lund',
'Umeå',
'Gävle',
'Södertälje',
'Borås',
'Huddinge',
'Eskilstuna',
'Nacka',
'Halmstad',
'Sundsvall',
'Södertälje',
'Växjö',
'Karlstad',
'Haninge',
'Kristianstad',
'Kungsbacka',
'Solna',
'Järfälla',
'Sollentuna',
'Skellefteå',
'Kalmar',
'Varberg',
'Östersund',
'Trollhättan',
'Uddevalla',
'Nyköping',
'Skövde',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children: cities.map((city) {
return OutlineButton(
child: Text(city),
onPressed: (){
setState(() {
this.stackIndex = cities.indexOf(city);
});
},
);
}).toList()
),
IndexedStack(
index: stackIndex,
children: cities.map((city){
return myStreamBuilder(city);
})
)
],
),
),
);
}
Widget myStreamBuilder(String city){
Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
return Text(city);
}
}

Related

Flutter ListView.builder renders items in top-left corner

I need list view builder to generate tiles based on the number of documents there will be in firebase for now I am just trying to sort the UI. I dont understand why its breaking. Image 1 is when the ListView.buidler is commented out. Image 2 is leaving ListView in.
List item
import 'package:flutter/material.dart';
import 'package:track/src/widgets/admin_navbar.dart' as widgets;
import 'package:track/src/widgets/colour_icon_button.dart' as widgets;
import 'package:track/src/features/clients/domain/client_firebase_storage.dart';
class ClientsPage extends StatefulWidget {
const ClientsPage({Key? key}) : super(key: key);
#override
State<ClientsPage> createState() => _ClientsPageState();
}
class _ClientsPageState extends State<ClientsPage> {
late final ClientFirebaseStorage _clientsService;
late double screenWidth;
late double screenHeight;
#override
void initState() {
_clientsService = ClientFirebaseStorage();
super.initState();
}
#override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
title: const FlutterLogo(),
),
drawer: const widgets.AdminNavBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Clients',
style: Theme.of(context).textTheme.headline1,
),
const SizedBox(
width: 30,
),
const widgets.ColourIconButton(icon: Icon(Icons.search_rounded)),
const SizedBox(
width: 5,
),
const widgets.ColourIconButton(
icon: Icon(Icons.swap_vert_rounded),
),
SizedBox(
width: screenWidth - 350,
),
const widgets.ColourIconButton(
icon: Icon(Icons.add),
),
],
),
SizedBox(
height: 190,
),
Text('Test1'),
Text('Test2'),
Text('Test3'),
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
onTap: () {},
title: Text('#'),
);
},
)
// StreamBuilder(
// stream: _clientsService.allClients(),
// builder: (context, snapshot) {
// switch (snapshot.connectionState) {
// case ConnectionState.waiting:
// case ConnectionState.active: //implicit fall through
// if (snapshot.hasData) {
// final allClients = snapshot.data as Iterable<Client>;
// return ClientsListView(
// clients: allClients,
// onTap: (clients) {},
// );
// } else {
// return const CircularProgressIndicator();
// }
// default:
// return const CircularProgressIndicator();
// }
// },
// ),
],
),
);
}
}
Before adding List.viewbuilder
After adding list.viewbuilder
for the first picture (before adding Listview.builder) items are rendered in center because you have a Row inside your Column, Column & Row have a default CrossAxisAlignment.center
After adding the ListView.builder, the log will be showing you an error, ListView here needs to be either inside an Expanded or shrinkWrap: true,
Setting an Expanded as a parent for the ListView will make the listview scrollable only, but adding the attribute shrinkWrap: true will stop the scrolling feature in your Listview, and then you will have to put your Column inside a Listview or SingleChildScrollView

Exception caught by widgets library Incorrect use of ParentDataWidget

When I am trying to display the data present in firebase realtime database. I am getting the error stating Exception caught by widgets library Incorrect use of ParentDataWidget.
class NotificationView extends StatefulWidget {
const NotificationView({Key key}) : super(key: key);
#override
State<NotificationView> createState() => _NotificationViewState();
}
class _NotificationViewState extends State<NotificationView> {
Map data;
List key;
#override
void initState() {
fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: Container(
child: FutureBuilder(
future: fetchData(),
builder: (context, snapshot) {
if (data != null) {
return ListView.builder(
itemCount: data.values.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
child: Card(
margin: EdgeInsets.fromLTRB(15, 5, 15, 15),
color: Colors.yellow[100],
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(
margin: EdgeInsets.fromLTRB(15, 5, 15, 15),
child: Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(data[key[index]]['title']),
SizedBox(height: size.height * 0.01),
Text(data[key[index]]['message']),
],
),
),
),
),
);
});
} else {
return Center(child: CircularProgressIndicator());
}
})));
}
fetchData() async {
var userId = SharedUtils.getString('UserId');
final ref = FirebaseDatabase.instance.ref();
final snapshot =
await ref.child('users/62cfc3faf3e5df6648d32684/inApp').get();
debugPrint(snapshot.key + 'KEyyyyyyyyyyyyyyyyyyyyy');
data = snapshot.value;
key = data.keys.toList();
debugPrint(
'Listttttttttttttttofffffffffffkeyyyyyyyyyyyyyy&&&77' + key.toString());
}
}
You are using "Expanded" as the child of the container which is wrong. Be aware that, you can use the "Expanded" widget only as the child of columns, rows, and flex. That's why you are getting this "Incorrect use of ParentDataWidget".
More details for Expanded widget.

Flutter: Use AbsorbPointer without rebuilding entire widget tree

I have a Stateful home page which has a list of Stateful widget children. When I click on a child, I'm gonna call its setState() to add a CircularProgressIndicator to it. That's all fine and dandy; clicking on a child only rebuilds that child.
However, I also have my home page wrapped inside an AbsorbPointer, and I want to set absorbing = true when I click on a child widget. The goal is to stop the user from clicking around while the app is doing some async work in the background. The problem now is that if I call setState() in the home page to set "absorbing" to true, it will rebuild all of the child widgets.
I could pass some parameters into the child widgets so that only the one I clicked on will have a CircularProgressIndicator, but even then all the other children will still be rebuilt.
I guess this boils down to the fact that I can't call setState() on a parent widget without rebuilding all the children, even though the parameter I pass to that setState() (absorbing) has nothing to do with those children.
Is there a workaround for this?
Thanks!
// home_screen.dart
class HomeScreen extends StatefulWidget {
static const String routeName = "homeScreen";
final MyUser? user;
const HomeScreen({Key? key, required this.user}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final MyDatabase _db = MyDatabase();
MyUser? _me;
int _currentPage = -1;
bool _isLoading = false;
...
#override
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: _isLoading,
child: Container(
decoration: BoxDecoration(
// color: Color(0xFF0d0717),
image: DecorationImage(
image: Image.asset(
'assets/background.png',
).image,
fit: BoxFit.cover,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: ...,
bottomNavigationBar: ...,
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder<QuerySnapshot>(
stream: _db.getLiveChannels(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
// print("Has no data");
return Center(
child: CircularProgressIndicator(),
);
}
_channels.addAll(List.generate(
snapshot.data!.docs.length,
(index) => Channel.fromSnapshot(
snapshot.data!.docs[index])));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Now Playing',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 8.0),
LiveIndicator(),
],
),
SizedBox(height: 8.0),
Container(
height: 250,
child: PageView.builder(
physics: PageScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: PageController(
viewportFraction: .9,
),
itemCount: _channels.length,
itemBuilder: (context, index) {
Channel channel =
_channels[_channels.length - 1 - index];
return ChildWidget(
callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
);
},
),
),
...,
],
);
},
),
...,
],
),
),
),
),
),
);
}
Future<void> _callback(params) async {
if (_isLoading == false) {
setState(() {
_isLoading = true;
_currentPage = index;
});
}
someAsyncMethod().then((_) => setState(() {
_isLoading = false;
_currentPage = -1;
}));
}
}
// child_widget.dart
class ChildWidget extends StatefulWidget {
final Future<void> Function(params) callback;
final bool loading;
const ChildWidget({
Key? key,
required this.callback,
required this.loading,
}) : super(key: key);
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
late Future<void> Function(params) callback;
late bool loading;
#override
void initState() {
super.initState();
callback = widget.callback;
loading = widget.loading;
}
#override
Widget build(BuildContext context) {
return Padding(
child: Column(
children: [
Expanded(
child: CustomClickableWidget(
onPressed: callback,
child: Expanded(
child: Container(
child: Stack(
children: [
...,
if (loading) ...[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
],
],
),
),
),
),
),
...,
],
),
);
}
}
Screenshot
The SetState function triggers the Build() function, so all the code present in the Build() function will be executed again. I don't quite see why this is a problem for you ?
On the other hand in your code I see that for your child you have defined a key: UniqueKey (). When the build function will run after SetState (), it will create a new child without keeping the state of the previous child. You shouldn't define the UniqueKey () in your function but rather as an instance variable of your state
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: UniqueKey(),
)
You should define you key here
class _ChildWidgetState extends State<ChildWidget> {
UniqueKey myKey = UniqueKey();
and you function
ChildWidget(callback: _callback;
loading: (_isLoading && _currentPage == index),
key: myKey,
)

How to create a card in a listview with button press on Flutter?

Listview will be empty after the click I would like to create a card in the ListView on flutter.
Is it also possible to create a dynamic home page? For example when there will be no any card on the list it is going to write on the background there is no any card yet. But if card created this indication will be deleted.
Could you please support me regarding this topic?
import 'package:flutter/material.dart';
class ListScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
bool _isLoading = true;
List<String> _items = [];
#override
void initState() {
super.initState();
_getListData();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
margin: EdgeInsets.all(10),
child: !_isLoading && _items.isEmpty
? Center(
child: Text("No data found"),
)
: (_isLoading && _items.isEmpty)
? Container(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.pink),
),
),
)
: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return _createListRow(_items[index], index);
},
),
),
),
);
}
_createListRow(String item, int index) {
return Card(
elevation: 3,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(item),
FlatButton(
child: Text("Delete"),
onPressed: () {
setState(() {
_items.removeAt(index);
});
},
)
],
),
);
}
_getListData() {
// Create dynamic list
Future.delayed(Duration(milliseconds: 500));
setState(() {
_items.add("First");
_items.add("Second");
_items.add("Third");
_isLoading = false;
});
}
}
You should check the official documentation. It's not so hard to learn with it :
ListView
Card
InkWell

Flutter: ScrollController initialScrollOffset not working

I'm trying to initialize a SingleChildScrollView to start at a certain position with a custom ScrollController. I thought I could use initialScrollOffset and set an initial value in the init method. But somehow when the SingleChildScrollView renders, it only jumps to initialOffset at first build, then when I navigate to another instance of this Widget it doesn't jump to the initialOffset position.
I don't know why, and if I'm lucky maybe one of you have the answer.
Here's my code:
class Artiklar extends StatefulWidget {
final String path;
final double arguments;
Artiklar({
this.path,
this.arguments,
});
#override
_ArtiklarState createState() => _ArtiklarState(arguments: arguments);
}
class _ArtiklarState extends State<Artiklar> {
final double arguments;
_ArtiklarState({this.arguments});
ScrollController _scrollController;
double scrollPosition;
#override
void initState() {
super.initState();
double initialOffset = arguments != null ? arguments : 22.2;
_scrollController = ScrollController(initialScrollOffset: initialOffset);
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final bool isAdmin = Provider.of<bool>(context) ?? false;
var pathElements = widget.path.split('/');
String tag;
if (pathElements.length == 2) {
tag = null;
} else if (pathElements.length == 3) {
tag = pathElements[2];
} else {
tag = null;
}
return StreamBuilder<List<ArtikelData>>(
stream: DatabaseService(tag: tag).artiklarByDate,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return GlobalScaffold(
body: SingleChildScrollView(
child: Container(
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GradientHeading(text: "Artiklar", large: true),
isAdmin
? NormalButton(
text: "Skapa ny artikel",
onPressed: () {
Navigator.pushNamed(
context, createNewArtikelRoute);
},
)
: Container(),
SizedBox(height: 10),
SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: TagList(path: tag),
),
SizedBox(height: 10),
LatestArtiklar(
snapshot: snapshot,
totalPosts: snapshot.data.length,
numberOfPosts: 10,
),
],
),
),
),
),
),
);
} else if (!snapshot.hasData) {
return GlobalScaffold(
body: SingleChildScrollView(
child: Container(
child: Center(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GradientHeading(text: "Artiklar", large: true),
SizedBox(height: 10),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: TagList(path: tag),
),
SizedBox(height: 10),
LatestArtiklar(hasNoPosts: true)
],
),
),
),
),
),
);
} else {
return GlobalScaffold(
body: Center(child: CircularProgressIndicator()),
);
}
},
);
}
}
That's because that widget is already built on the tree and thus, initState won't be called again for that widget.
You can override the didUpdateWidget method that will trigger each time that widget is rebuilt and make it jump on there, for example.
#override
void didUpdateWidget(Widget old){
super.didUpdateWidget(old);
_scrollController.jumpTo(initialOffset);
}
keepScrollOffset: false
If this property is set to false, the scroll offset is never saved and initialScrollOffset is always used to initialize the scroll offset.