web-dev-qa-db-fra.com

Flutter - Comment mettre à jour l'état (ou la valeur?) D'une future / liste utilisée pour créer ListView (via futeBuilder)

Je colle le code pertinent ci-dessous, mais vous pourrez peut-être y répondre en fonction de ma pseudo-explication.

Je suis à l'aide d'un futurbuilder pour créer une liste de réception.

  • Je démarre à l'aide d'Init () à ASYNC HTTP appelez l'API et analysez que dans une liste d'objets (emplacement) mappée pour représenter le résultat JSON.
  • Cette liste de localisation est ensuite renvoyée dans le Future<List<Location>> _listFuture variable (qui est l'avenir pour le futurbuilder).
  • Une fois que le futur "retourne" ou "finitions", le futurbuilder commence et utilise listview.builder/conteneur/listtile pour boucler et construire la liste.
  • À un moment donné, je vais vouloir un gestionnaire ONTAP () (dans la liste) qui modifie la couleur de fond de tout élément de liste sélectionné.
  • Pour soutenir cela, j'ai un membre de l'arrière-plan dans la classe de localisation (qui détient les réponses JSON) que je par défaut sur "# FC7303" pour tous les éléments (supposons que tout est toujours initialement décoché). Je veux ensuite changer le fond de tout ce qui est choisi pour "# 34bdeb" dans ONTAP ().
  • Je suppose que je pouvais appeler le SSTATE () qui déclencherait une rafraîchissement et la nouvelle couleur d'arrière-plan serait remarquée/utilisée sur Redraw.

Le problème est que le listview/Contre -erer/Listtile est conduit par un

Future<List<Location>>

. Je peux transmettre l'index "taraudé" à mon gestionnaire ONTAP, mais je ne crois pas que je puisse avoir mon _Changebackground () juste mettre à jour la valeur de l'arrière-plan pour l'index sélectionné et la création d'appels (), car vous ne pouvez pas accéder à/mettre à jour directement un avenir comme ça ( Je reçois l'erreur ERROR: The operator '[]' isn't defined for the class 'Future<List<Location>>'.)

Je ne suis pas sûr que je prends la bonne approche. Dans ce cas, je suppose que je pouvais toujours séparer théoriquement le suivi des couleurs "fond" dans une nouvelle liste séparée (en dehors du futur) et suivre/référencer-la de cette manière en utilisant des index alignés de l'ONTAP ().

Cependant, je ne suis pas sûr que cela fonctionnerait toujours. À l'avenir, je pourrais avoir besoin de changer les valeurs/l'état de ce qui a été retourné à l'avenir. Par exemple, pensez à si je voulais pouvoir cliquer sur un élément de la liste et mettre à jour le "Nom d'entreprise". Dans ce cas, je modifierais une valeur stockée dans le futur directement. Je suppose que je pourrais techniquement envoyer le nouveau nom au serveur et rafraîchir complètement la liste de cette façon, mais cela semble inefficace (que s'ils décident de "annuler" et non d'enregistrer les modifications?).

Toute aide est appréciée. Merci!

cette classe détient en fait les données pertinentes de la liste

// Location
class Location {

  // members
  String locationID;
  String locationName;
  String companyName;
  String backgroundColor = 'fc7303';

  // constructor?
  Location({this.locationID, this.locationName, this.companyName});

  // factory?
  factory Location.fromJson(Map<String, dynamic> json) {
    return Location(
      locationID: json['locationID'],
      locationName: json['locationName'],
      companyName: json['companyName'],
    );

  }

}

cette classe est la réponse parent JSON qui a des messages "Résultats" (Success/Error). Il instancite de la classe ci-dessus en tant que liste de suivre les enregistrements d'entreprise/emplacement réels

//jsonResponse
class jsonResponse{

  String result;
  String resultMsg;
  List<Location> locations;

  jsonResponse({this.result, this.resultMsg, this.locations});

  factory jsonResponse.fromJson(Map<String, dynamic> parsedJson){

    var list = parsedJson['resultSet'] as List;
    List<Location> locationList = list.map((i) => Location.fromJson(i)).toList();
    return jsonResponse(
        result: parsedJson['result'],
        resultMsg: parsedJson['resultMsg'],
        locations: locationList
    );
  }

} // jsonResponse

voici les widgets d'état et d'états qui utilisent les classes ci-dessus pour analyser les données API et créer ListView

class locationsApiState extends State<locationsApiWidget> {

  // list to track AJAX results
  Future<List<Location>> _listFuture;

  // init - set initial values
  @override
  void initState() {
    super.initState();
    // initial load
    _listFuture = updateAndGetList();
  }

  Future<List<Location>> updateAndGetList() async {

    var response = await http.get("http://XXX.XXX.XXX.XXX/api/listCompanies.php");
    if (response.statusCode == 200) {
      var r1 = json.decode(response.body);
      jsonResponse r = new jsonResponse.fromJson(r1);
      return r.locations;
    } else {
      throw Exception('Failed to load internet');
    }

  }

  _changeBackground(int index){
    print("in changebackground(): ${index}");       // this works!
    _listFuture[index].backgroundColor = '34bdeb';   // ERROR: The operator '[]' isn't defined for the class 'Future<List<Location>>'.
  }

  // build() method
  @override
  Widget build(BuildContext context) {

    return new FutureBuilder<List<Location>>(
        future: _listFuture,
        builder: (context, snapshot){

          if (snapshot.connectionState == ConnectionState.waiting) {
            return new Center(
              child: new CircularProgressIndicator(),
            );
          } else if (snapshot.hasError) {
            return new Text('Error: ${snapshot.error}');
          } else {
            final items = snapshot.data;
            return new Scrollbar(
              child: new RefreshIndicator(
                  child: ListView.builder(
                    physics: const AlwaysScrollableScrollPhysics(),
                    //Even if zero elements to update scroll
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return
                        Container(
                            color: HexColor(items[index].backgroundColor),
                            child:
                            ListTile(
                              title: Text(items[index].companyName),
                              onTap: () {
                                print("Item at $index is ${items[index].companyName}");
                                _changeBackground(index);
                              }  // onTap
                            )
                        );
                    },
                  ),
                  onRefresh: () {
                    // implement later
                    return;
                  } // refreshList,
              ),
            );
          }// else
        } // builder
    ); // FutureBuilder
  } // build
} // locationsApiState class


class locationsApiWidget extends StatefulWidget {
  @override
  locationsApiState createState() => locationsApiState();
}

classe d'assistance (prise de quelque part sur Stackoverflow) pour convertir HEX en des couleurs entière

class HexColor extends Color {
  static int _getColorFromHex(String hexColor) {
    hexColor = hexColor.toUpperCase().replaceAll("#", "");
    if (hexColor.length == 6) {
      hexColor = "FF" + hexColor;
    }
    return int.parse(hexColor, radix: 16);
  }

  HexColor(final String hexColor) : super(_getColorFromHex(hexColor));
}

Merci!

3
user3249281

Ce que je recommanderais, c'est supprimer la couleur de fond de votre classe d'emplacement et que vous stockez plutôt dans votre état que les emplacements sont sélectionnés. De cette façon, votre liste de localisation n'a pas besoin de changer lorsque des éléments sont sélectionnés. Je créerais également un apatridementWidget pour votre élément de localisation, qui définirait la couleur d'arrière-plan, selon qu'elle soit sélectionnée ou non. Alors:

// for the LocationItem widget callback
typedef void tapLocation(int index);

class locationsApiState extends State<locationsApiWidget> {

  // list to track AJAX results
  Future<List<Location>> _listFuture;
  final var selectedLocationIndices = Set<int>();

  // init - set initial values
  @override
  void initState() {
    super.initState();
    // initial load
    _listFuture = updateAndGetList();
  }

  Future<List<Location>> updateAndGetList() async {

    var response = await http.get("http://XXX.XXX.XXX.XXX/api/listCompanies.php");
    if (response.statusCode == 200) {
      var r1 = json.decode(response.body);
      jsonResponse r = new jsonResponse.fromJson(r1);
      return r.locations;
    } else {
      throw Exception('Failed to load internet');
    }
  }

  void _toggleLocation(int index) {
    if (selectedLocationIndices.contains(index))
      selectedLocationIndices.remove(index);
    else
      selectedLocationIndices.add(index);
  }

  // build() method
  @override
  Widget build(BuildContext context) {

    return new FutureBuilder<List<Location>>(
        future: _listFuture,
        builder: (context, snapshot){

          if (snapshot.connectionState == ConnectionState.waiting) {
            return new Center(
              child: new CircularProgressIndicator(),
            );
          } else if (snapshot.hasError) {
            return new Text('Error: ${snapshot.error}');
          } else {
            final items = snapshot.data;
            return new Scrollbar(
              child: new RefreshIndicator(
                  child: ListView.builder(
                    physics: const AlwaysScrollableScrollPhysics(),
                    //Even if zero elements to update scroll
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return LocationItem(
                        isSelected: selectedLocationIndices.contains(index),
                        onTap: () => setState({
                          _toggleLocation(index);
                        })
                      );
                    },
                  ),
                  onRefresh: () {
                    // implement later
                    return;
                  } // refreshList,
              ),
            );
          }// else
        } // builder
    ); // FutureBuilder
  } // build
} // locationsApiState class


class locationsApiWidget extends StatefulWidget {
  @override
  locationsApiState createState() => locationsApiState();
}

Et la liste de liste d'articles:

class LocationItem extends StatelessWidget {

  final bool isSelected;
  final Function tapLocation;

  const LocationItem({@required this.isSelected, @required this.tapLocation, Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: isSelected ? HexColor('34bdeb') : HexColor('fc7303'),
      child: ListTile(
        title: Text(items[index].companyName),
        onTap: () => tapLocation() // onTap
      )
    );
  }
}

Pardonne-moi, je ne peux pas compiler donc j'espère que c'est correct. Mais je pense que vous obtenez l'idée: Demandez au widget sotculier de garder une trace des emplacements sélectionnés séparément et laissez l'emplacement décider de la manière de se rendre quand elle est reconstruite.

1
spenster