Search code examples
flutterflutter-listview

My list is not showing up on search page after generating values for list on page start. Why?


I am making a search page in flutter. I have sucsessfully added the search bar, but now adding the corresponding list is becoming an issue. The data that the user searches is from a json file, so I have sucsessfully programmed converting the json file to the correct internal data type and have stuctured the code so that the list would be filled before the building of the page. However, this seems to be not working and and the page is building before the parsing of the json is complete. Any ideas on how top proceed would be much appreciated.

I have tried using init state and putting the json read fuction in the beginning of the build fuction. Neither of these have worked. I have considered createing the list in a previous page and passing it to the search page, but I would much rather have it done in the page itself.

Below is the code I have for the page as of now.

import 'dart:convert';

import 'package:dnd_app/Models/fighter.dart';
import 'package:dnd_app/Models/monster.dart';
import 'package:flutter/material.dart';
import 'package:dnd_app/constants.dart' as constants;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:dnd_app/Models/pc.dart';
import 'package:flutter_svg/svg.dart';

class monsterSearch extends StatefulWidget {
  const monsterSearch({super.key});

  @override
  State<monsterSearch> createState() => _monsterSearchState();
}

class _monsterSearchState extends State<monsterSearch> {
  List<Monster> monsters = [];
  int mom = 10;

  @override
  void initState() {
    readJson();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print(monsters.length);
    return Scaffold(
      appBar: constants.titleBar("Monster Search"),
      backgroundColor: constants.greyGold(),
      body: Column(
        children: [
          searchBar(),
          Expanded(
              child: ListView.builder(
            scrollDirection: Axis.vertical,
            itemCount: monsters.length,
            itemBuilder: (context, index) {
              return Text(monsters[index].name);
            },
          ))
        ],
      ),
    );
  }

  Future<void> readJson() async {
    final String responce =
        await rootBundle.loadString('assets/5e-SRD-Monsters.json');
    final retData = jsonDecode(responce);
    for (var mon in retData) {
      try {
        String sSpeed = mon["speed"];
        sSpeed = sSpeed.substring(0, sSpeed.indexOf(' '));
        monsters.add(Monster(
            mon["name"],
            mon["hit_points"],
            mon["armor_class"],
            mon["strength"],
            mon["constitution"],
            mon["dexterity"],
            mon["intelligence"],
            mon["wisdom"],
            mon["charisma"],
            int.parse(sSpeed)));
      } on Error catch (_) {
        continue;
      } on Exception catch (_) {
        continue;
      }
    }
    print(monsters.length);
  }

  void waitingGame() async {
    await readJson();
  }

  Container searchBar() {
    return Container(
      margin: EdgeInsets.only(top: 40, left: 20, right: 20),
      decoration: BoxDecoration(
          boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.11))]),
      child: TextField(
        decoration: InputDecoration(
            filled: true,
            fillColor: Colors.white,
            hintText: 'Search Monster',
            contentPadding: EdgeInsets.all(15),
            prefixIcon: Padding(
              padding: const EdgeInsets.all(12),
              child: SvgPicture.asset('assets/pictures/icons8-search.svg'),
            ),
            border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(15),
                borderSide: BorderSide.none)),
      ),
    );
  }
}

Solution

  • Have you tried to wrap

    Expanded(
      child: ListView.builder(
        scrollDirection: Axis.vertical,
        itemCount: monsters.length,
        itemBuilder: (context, index) {
          return Text(monsters[index].name);
        },
      )
    )
    

    inside a FutureBuilder where the future will be your readJson. Would look something like this:

    return FutureBuilder(
          future: viewModel.getStories,
          builder: (context, snapshot) {
            if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
              List<Monster> futureBuilderMonsters = snapshot.data as List<Monster>;
              return Expanded ...
            }
    
            return const Center(
              child: SizedBox(width: 64, height: 64, child: CircularProgressIndicator()),
            );
          },
      );
    

    Your readJson should return Future<List<Monsters>> so the FutureBuilder knows when data is existing. Your "old" Monster list can stay in case you need it somewhere else