Reputation: 640
I've got a ListView.builder that is displaying a list of ListTiles with data from a Supabase realtime stream. I would like to conditionally show/hide some of the tiles based on a flag in the stream data.
The stream is a list of maps (Stream<List<Map<String,dynamic>>>) and one of the keys in each map is "isArchived". The UI has a switch ("Active/All") and I only want to show the "isArchived" tiles if the switch is set to "All".
Put another way - only the "Active" (isArchived = false) maps should be displayed if the switch is set to "Active", otherwise all the tiles can be displayed.
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator.adaptive(); //* <- waiting
} else if (snapshot.hasError) {
return Text('Error! ${snapshot.error}'); //* <- error
} else if (!snapshot.hasData) {
return Text('No data present!'); //* <- success w/o data
} else {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${snapshot.data?[index]['name']}'),
subtitle: snapshot.data?[index]['archived']
? Text('Archived')
: Text('Active'),
trailing: Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
TxnsByJob(snapshot.data?[index]['name'])));
},
);
});
I can't quite figure out how to get this done. Any help would be appreciated.
Upvotes: 0
Views: 46
Reputation: 640
Here's how I ended up getting this to work...
int? switchValue = 0; // initial segmented control value
...
onValueChanged: (int? newValue) {
setState(() { switchValue = newValue; }); }
...
// inside ListView.builder...
itemCount: snapshot.data!.length,
builder: (context, index) {
if (switchValue == 0) { // 0 = "show Active only"
switch (snapshot.data![index]['archived']) {
case false:
return ListTile(... // build full listTile
default:
return SizedBox.shrink(); // zero-size/empty
} else { // switchValue is 1, so "show all"
return ListTile(...
}
Seemed like the simplest solution. Thanks to the answers above, though - I learned all about the .where
functionality, and about SizedBox.shrink()
, which I'd never seen before.
Now if I can just figure out a way to smoothly animate the transition between Active only / Show all (instead of the abrupt change), I'll be rolling.
Upvotes: 0
Reputation: 910
Below is the alternative solution if you like,
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Toggle Items',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: YourWidget(), // This is where your main widget is set
);
}
}
class YourWidget extends StatelessWidget {
// Use a ValueNotifier to control the state of the switch
final ValueNotifier<bool> showAllNotifier = ValueNotifier<bool>(true);
// This simulates your stream data
final Stream<List<Map<String, dynamic>>> _stream = Stream.value([
{'name': 'Item 1', 'archived': false},
{'name': 'Item 2', 'archived': true},
{'name': 'Item 3', 'archived': false},
{'name': 'Item 4', 'archived': true},
]);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Toggle Items'),
),
body: Column(
children: [
// Switch to toggle between showing all items or only active items
ValueListenableBuilder<bool>(
valueListenable: showAllNotifier,
builder: (context, showAll, child) {
return SwitchListTile(
title: Text('Show All Items'),
value: showAll,
onChanged: (bool value) {
showAllNotifier.value = value;
},
);
},
),
// StreamBuilder to display the list of items
Expanded(
child: StreamBuilder<List<Map<String, dynamic>>>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error! ${snapshot.error}');
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Text('No data present!');
} else {
return ValueListenableBuilder<bool>(
valueListenable: showAllNotifier,
builder: (context, showAll, child) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final item = snapshot.data![index];
final bool isArchived = item['archived'];
// Only show items if showAll is true or the item is not archived
if (showAll || !isArchived) {
return ListTile(
title: Text('${item['name']}'),
subtitle: isArchived
? Text('Archived')
: Text('Active'),
trailing: Icon(Icons.chevron_right),
onTap: () {
// Navigate to a detailed page
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => TxnsByJob(item['name']),
));
},
);
} else {
// Return an empty container if the item is hidden
return SizedBox.shrink();
}
},
);
},
);
}
},
),
),
],
),
);
}
}
// Replace TxnsByJob with your actual detailed page
class TxnsByJob extends StatelessWidget {
final String name;
TxnsByJob(this.name);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details for $name')),
body: Center(child: Text('Details for $name')),
);
}
}
Upvotes: 0
Reputation: 355
Demo Video for the result : Filter_Data_with_switch_button
To filter the data according to the Switch
button first create a bool
to trigger the filter value:
bool showArchived = false;
then create the Switch
button Widget
and add its functionalities
Switch(
value: showArchived,
onChanged: (value) {
setState(() {
showArchived = value;
});
},
);
Then you can simply filter the snapShot.data!
as a List<Map<String, dynamic>>
then use the filtered data in the ListTile
, this is the filter method :
List<Map<String, dynamic>> archivedData = snapshot.data!.where(
(Map<String, dynamic> item) {
bool isArchived = showArchived ? item['archived'] : true;
return isArchived;
},
).toList();
This is the Full Code
StreamBuilder(
stream: _stream,
builder: (context, AsyncSnapshot<List<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator.adaptive(); //* <- waiting
} else if (snapshot.hasError) {
return Text('Error! ${snapshot.error}'); //* <- error
} else if (!snapshot.hasData) {
return const Text('No data present!'); //* <- success w/o data
} else {
List<Map<String, dynamic>> archivedData = snapshot.data!.where(
(Map<String, dynamic> item) {
bool isArchived = showArchived ? item['archived'] : true;
return isArchived;
},
).toList();
return Expanded(
child: ListView.builder(
itemCount: archivedData.length,
itemBuilder: (context, index) {
Map<String, dynamic> item = archivedData[index];
return ListTile(
title: Text('${item['name']}'),
subtitle: item['archived']
? const Text('Archived')
: const Text('Active'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TxnsByJob(
item['name'],
),
),
);
},
);
},
),
);
}
},
);
Upvotes: 0