Home > Software engineering >  Flutter Dynamically build cards base on http response
Flutter Dynamically build cards base on http response

Time:12-15

I would like to create cards based on a http response when a button is clicked, and I would like the "Search Item" and "Search Button" stay on top while the card list still can scroll, please help.

I tried to use FutureBuilder widget but it loads data when the page loads the first time (without button being pressed).

class clientRecord {
  final String englishName;
  final String chineseName;

  const clientRecord({
    required this.englishName,
    required this.chineseName,
  });

  factory clientRecord.fromJson(Map<String, dynamic> json) {
    return clientRecord(
      englishName: json['Ename'] as String,
      chineseName: json['Cname'] as String,
    );
  }
}

class homePage extends StatefulWidget {
  @override
  _homePageState createState() => _homePageState();
}

class _homePageState extends State<homePage> {
  final _searchItemController = TextEditingController();

  List<clientRecord> parseJson(String responseBody) {
    final parsed =
        convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
    return parsed
        .map<clientRecord>((json) => clientRecord.fromJson(json))
        .toList();
  }

  Future<List<clientRecord>> fetchData(http.Client client, _searchItem) async {
    final response = await client
        .get(Uri.parse('test.php'));
    return parseJson(response.body);;
  }

  @override
  void dispose() {
    _searchItemController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Client List')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(0, 30, 0, 20),
              child: Text(
                adminPassword(),
                style: TextStyle(
                    fontSize: 30,
                    fontWeight: FontWeight.bold,
                    color: Colors.orange[800]),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(12.0),
              child: TextField(
                decoration: InputDecoration(
                    border: OutlineInputBorder(), labelText: 'Search Item'),
                controller: _searchItemController,
              ),
            ),
            SizedBox(height: 20),
            Container(
              height: 45,
              width: 250,
              decoration: BoxDecoration(
                  color: Colors.teal, borderRadius: BorderRadius.circular(16)),
              child: TextButton(
                onPressed: () {
                  fetchData(http.Client(), _searchItemController.text);
                },
                child: Text(
                  'Search',
                  style: TextStyle(color: Colors.white, fontSize: 20),
                ),
              ),
            ),
            FutureBuilder<List<clientRecord>>(
              future: fetchData(http.Client(), _searchItemController.text),
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return const Center(
                    child: Text('An error has occurred!'),
                  );
                } else if (snapshot.hasData) {
                  return buildBody(dl: snapshot.data!);
                } else {
                  return const Center(
                    child: CircularProgressIndicator(),
                  );
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}


class buildBody extends StatelessWidget {
  buildBody({super.key, required this.dl});

  final List<clientRecord> dl;

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
        scrollDirection: Axis.vertical,
        controller: _scrollController,
        child: Column(
          children:
          dl.map((dataRecord) => dataCard(dataRecord, context)).toList(),
        ));
  }
}
Widget dataCard(dataRecord, c) {
  return GestureDetector(
    onTap: () {
      // Navigator.push(
      //   c,
      //   MaterialPageRoute(builder: (c) => newdevelopmentdetail(dataRecord.id)),
      // );
    },
    child: Card(
      color: Colors.lightGreen[100],
      child: SizedBox(
        height: 100,
        child: Row(
          children: [
            Text(dataRecord.englishName),
          ],
        ),
      ),
    ),
  );
}



CodePudding user response:

You have two separate questions here - about Future builder use; and keeping Search Item and Search Button on top. I'll try to answer the first question.

Your Future Builder fires because you tell it to: you call it in FutureBuilder itself:

FutureBuilder<List<clientRecord>>(
              future: fetchData(http.Client(), _searchItemController.text),

What you can do is introduce a separate Future variable, and based on it being null or not - you create your future builder. Something like:

Future<List<clientRecord>>? myFuture;

...

child: Column(
          children: [
...
  if (myFuture!=null) FutureBuilder<List<clientRecord>>(
              future: myFuture
 ...
else Text('Press button to get list')

And your button press should do something like (with setState being called to trigger rebuild):

TextButton(
                onPressed: () {
                  myFuture=fetchData(http.Client(), _searchItemController.text);
                  setState((){});
                },

CodePudding user response:

By adding a separate Future List and assign to future of FutureBuilder, setState after http call works fine.

For scrollable cards without scroll search items, use ListView wrapped inside Expanded wrapped inside Column works perfectly.

  • Related