Andy
Andy

Reputation: 640

How Can I Show/Hide ListTiles in Flutter ListView Based on Condition?

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

Answers (3)

Andy
Andy

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

ABV
ABV

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

Mahmoud Al-shehyby
Mahmoud Al-shehyby

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

Related Questions