skip to Main Content

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)),
      ),
    );
  }
}

2

Answers


  1. 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

    Login or Signup to reply.
  2. The issue you’re facing is that the page is building before the parsing of the JSON is complete. To solve this problem, we can use the FutureBuilder widget to handle asynchronous operations and wait for the JSON parsing to complete before building the page. Here’s how you can modify your code:

    1. Wrap your ListView.builder with a FutureBuilder widget and provide the readJson function as the future parameter.
    FutureBuilder<void>(
      future: readJson(),
      builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          // show a loading indicator while parsing the JSON because its cooler
          return CircularProgressIndicator();
        } else if (snapshot.hasError) {
          // handle any errors that occurred during parsing
          return Text('error happened!! -> ${snapshot.error}'); // hope you never see this message
        } else {
          // build the ListView using the parsed data
          return ListView.builder(
            scrollDirection: Axis.vertical,
            itemCount: monsters.length,
            itemBuilder: (context, index) {
              return Text(monsters[index].name);
            },
          );
        }
      },
    )
    
    1. Use setState after adding the Monsters to trigger a rebuild of the widget tree.
    Future<void> readJson() async {
      final String response =
          await rootBundle.loadString('assets/5e-SRD-Monsters.json');
      final retData = jsonDecode(response);
      for (var mon in retData) {
        try {
          String sSpeed = mon["speed"];
          sSpeed = sSpeed.substring(0, sSpeed.indexOf(' '));
          setState(() {
            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;
        }
      }
    }
    

    With these changes, the FutureBuilder will wait for the readJson function to complete before building the ListView. While the JSON parsing is in progress, a loading indicator will be shown. If an error occurs during parsing, an error message will be displayed. Once the parsing is complete, the ListView will be built with the parsed data.

    Hope this helps your problem!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search