首页 > 解决方案 > Flutter:将列表保存为收藏夹

问题描述

我有如下所示的 JSON 数据文件,您可以看到“景点”本身就是每个城市下的列表:

[
    {
        "city": "text text text",
        "attractions": [
            "text text ",
            "text text"
        ],
    },
    {
        "city": "text text text",
        "attractions": [
            "text text",
            "text text",
        ],
    },
]

下面的代码基本上是一个从上述 JSON 中获取数据的列表视图构建器。现在,当单击该城市时,它会导航到下一页,其中会显示该城市的景点列表。正如您从代码中看到的那样,有一个选项可以将另一个页面中的城市保存为收藏夹,该收藏夹可以显示在另一个收藏夹列表中:

class Index extends StatefulWidget {
  @override
  _IndexState createState() => _IndexState();
}
List data;
List<Cities> citylist = List();
List<Cities> citysavedlist = List();
int index;
class _IndexState extends State<Index> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: listView(),
    );
  }
  Future<String> fetchData() async {
    String data =
        await DefaultAssetBundle.of(context).loadString("assets/data.json");
    final jsonResult = json.decode(data);
    this.setState(() {
      jsonResult
          .forEach((element) => citylist.add(new Cities.fromJson(element)));
    });
    return "Success!";
  }

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

  listView() {
    return ListView.builder(
      itemCount: citylist == null ? 0 : citylist.length,
      itemBuilder: (context, index) {
        return Column(
          children: <Widget>[_buildRow(index, citylist)],
        );
      },
    );
  }

  Widget _buildRow(index, citylist) {
    final bool alreadySaved = citysavedlist.contains(citylist[index]);
    return Padding(
      padding: const EdgeInsets.only(top: 5.0, left: 5.0, right: 5.0),
      child: Card(
        child: ListTile(
            title:
                Text(citylist[index].title, style: TextStyle(fontSize: 22.0)),
            trailing: IconButton(
              icon: Icon(
                alreadySaved ? Icons.star : Icons.star_border,
                color: alreadySaved ? Colors.blue : Colors.blue,
              ),
              onPressed: () {
                setState(() {
                  if (alreadySaved) {
                    citysavedlist.remove(citylist[index]);
                  } else {
                    citysavedlist.add(citylist[index]);
                  }
                });
              },
            ), //subtitle: Text(subtitle),
            onTap: () {
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => Detail(citylist[index])));
            }),
      ),
    );
  }


void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
          final Iterable<ListTile> tiles = citysavedlist.map(
            (Cities pair) {
              return ListTile(
                  title: Text(
                    pair.city,
                  ),
                  onTap: () {
                    Navigator.push(context,
                        MaterialPageRoute(builder: (context) => Detail(pair)));
                  });
            },
          );

          final List<Widget> divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();
          return Scaffold(
            appBar: AppBar(
              title: const Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
}

这是模型类:

List<Cities> citiesFromJson(String str) =>
    List<Cities>.from(json.decode(str).map((x) => Cities.fromJson(x)));
String citiesToJson(List<Cities> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Cities {
  Cities({
    this.city,
    this.attractions,
  });

  String city;
  List<String> attractions;

  factory Cities.fromJson(Map<String, dynamic> json) => Cities(
        city: json["city"],
        attractions: List<String>.from(json["attractions"].map((x) => x)),
      );

  Map<String, dynamic> toJson() => {
        "city": city,
        "attractions": List<dynamic>.from(attractions.map((x) => x)),
      };
}

下面的代码是我需要你帮助的地方,它是用于详细页面,当有人点击一个城市时,这个页面会以列表的形式显示每个城市的景点。在此页面中,我希望能够将景点保存为收藏夹,以便在另一个页面中显示。

class Detail extends StatefulWidget {
  final Cities cities;
  Detail(this.cities);
  @override
  _DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.cities.city),
      ),
      body: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                widget.cities.city,
                style: TextStyle(fontSize: 20),
              ),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: widget.cities.attractions.length,
                itemBuilder: (BuildContext context, int index) {
                  return Card(
                      child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(widget.cities.attractions[index]),
                  ));
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

我需要您的帮助,请添加此功能“将景点保存为收藏”。无论它属于哪个城市,我都需要在一个“已保存的景点页面”中显示所有已保存的景点。

有人可以帮忙吗

标签: flutterdartflutter-listview

解决方案


查看我制作的示例。以下是我添加的 json 我只是将数据更改为您的理解

[
   {
       "city": "Canada",
       "attractions": [
           "Niagara Falls: An Elegant View",
           "Whistler: Your Perfect Ski Resort"
       ]
   },
   {
       "city": "Germany",
       "attractions": [
           "Berlin",
           "Munich"
       ]
   }
]

您提供的 json 中的模型类:

// To parse this JSON data, do
//
//     final cities = citiesFromJson(jsonString);

import 'dart:convert';

List<Cities> citiesFromJson(String str) => List<Cities>.from(json.decode(str).map((x) => Cities.fromJson(x)));

String citiesToJson(List<Cities> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Cities {
    Cities({
        this.city,
        this.attractions,
    });

    String city;
    List<String> attractions;

    factory Cities.fromJson(Map<String, dynamic> json) => Cities(
        city: json["city"],
        attractions: List<String>.from(json["attractions"].map((x) => x)),
    );

    Map<String, dynamic> toJson() => {
        "city": city,
        "attractions": List<dynamic>.from(attractions.map((x) => x)),
    };
}

这是主要的 ui 和 miplementation

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Index(),
    );
  }
}

class Index extends StatefulWidget {
  @override
  _IndexState createState() => _IndexState();
}

List data;
List<Cities> citylist = List();
List<Cities> citysavedlist = List();
int index;

class _IndexState extends State<Index> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: listView(),
    );
  }

  Future<String> fetchData() async {
    String data =
        await DefaultAssetBundle.of(context).loadString("json/parse.json");
    final jsonResult = json.decode(data);
    this.setState(() {
      jsonResult
          .forEach((element) => citylist.add(new Cities.fromJson(element)));
    });
    return "Success!";
  }

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

  listView() {
    return ListView.builder(
      itemCount: citylist == null ? 0 : citylist.length,
      itemBuilder: (context, index) {
        return Column(
          children: <Widget>[_buildRow(index, citylist)],
        );
      },
    );
  }

  Widget _buildRow(index, citylist) {
    final bool alreadySaved = citysavedlist.contains(citylist[index]);
    return Padding(
      padding: const EdgeInsets.only(top: 5.0, left: 5.0, right: 5.0),
      child: Card(
        child: ListTile(
            title: Text(citylist[index].city, style: TextStyle(fontSize: 22.0)),
            trailing: IconButton(
              icon: Icon(
                alreadySaved ? Icons.star : Icons.star_border,
                color: alreadySaved ? Colors.blue : Colors.blue,
              ),
              onPressed: () {
                setState(() {
                  if (alreadySaved) {
                    citysavedlist.remove(citylist[index]);
                  } else {
                    citysavedlist.add(citylist[index]);
                  }
                });
              },
            ), //subtitle: Text(subtitle),
            onTap: () {
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => Detail(citylist[index])));
            }),
      ),
    );
  }

  void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
          final Iterable<ListTile> tiles = citysavedlist.map(
            (Cities pair) {
              return ListTile(
                  title: Text(
                    pair.city,
                  ),
                  onTap: () {
                    Navigator.push(context,
                        MaterialPageRoute(builder: (context) => Detail(pair)));
                  });
            },
          );

          final List<Widget> divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();
          return Scaffold(
            appBar: AppBar(
              title: const Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
}

class Detail extends StatefulWidget {
  final Cities cities;
  Detail(this.cities);
  @override
  _DetailState createState() => _DetailState();
}

class _DetailState extends State<Detail> {
  List<String> attractionsSavedList = List();

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

    getData();
  }

  /* 

  This is done using the shared Prefrences where the saving structure is String

  This is where key is the unique user id, this is because if you logout with another user then this will differentiate the user and get the string based on it. 

  I have saved  a Map<String,List<String>> as String encoded it and while using it decode the string which will give you the map.
   */

  getData() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    var stringvalue = sharedPreferences.getString("Id");
    print(stringvalue);
    if (stringvalue != null) {
      Map<String, dynamic> newMap = json.decode(stringvalue);
      print('This is the city selected : ${newMap[widget.cities.city]}');

      var newlist = newMap[widget.cities.city];
      if (newlist != null) {
        newlist.forEach((element) {
          print(element);
          attractionsSavedList.add(element);
        });
      }
    } else {
      print('No values to show');
    }

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.cities.city),
      ),
      body: Container(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(15.0),
                  child: GestureDetector(
                    onTap: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => SavedAttractions()));
                    },
                    child: Text(
                      'Saved Attractions',
                      style: TextStyle(
                        fontSize: 18,
                        decoration: TextDecoration.underline,
                      ),
                    ),
                  ),
                )
              ],
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                widget.cities.city,
                style: TextStyle(fontSize: 20),
              ),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: widget.cities.attractions.length,
                itemBuilder: (BuildContext context, int index) {
                  final bool attractionsExists = attractionsSavedList
                      .contains(widget.cities.attractions[index]);
                  return Card(
                      child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        Text(widget.cities.attractions[index]),
                        IconButton(
                          onPressed: () async {
                            SharedPreferences _prefs =
                                await SharedPreferences.getInstance();

                            setState(() {
                              if (attractionsExists) {
                                attractionsSavedList
                                    .remove(widget.cities.attractions[index]);

                                String uniqueid = _prefs.getString("Id");

                                Map<String, dynamic> mapvalue =
                                    json.decode(uniqueid);

                                List list = mapvalue[widget.cities.city];

                                String stringtoRemove;

                                list.forEach((element) {
                                  if (element ==
                                      widget.cities.attractions[index]) {
                                    stringtoRemove =
                                        widget.cities.attractions[index];
                                  }
                                });

                                if (attractionsSavedList.length == 0) {
                                  list.remove(stringtoRemove);

                                  if (list.length == 0) {
                                    mapvalue.remove(stringtoRemove);
                                    _prefs.setString(
                                        "Id", json.encode(mapvalue));
                                  }
                                } else {
                                  list.remove(stringtoRemove);
                                  mapvalue.remove(stringtoRemove);
                                  
                                  mapvalue[widget.cities.city] = list;

                                  _prefs.setString("Id", json.encode(mapvalue));
                                }
                              } else {
                                print(
                                    'It does not exist in the list ${attractionsSavedList.length}');
                                attractionsSavedList
                                    .add(widget.cities.attractions[index]);

                                var uniqueId = _prefs.getString("Id");
                                if (uniqueId != null) {
                                  Map<String, dynamic> newMap =
                                      json.decode(uniqueId);
                                  if (attractionsSavedList.length > 0) {
                                    print(widget.cities.city);

                                    newMap[widget.cities.city] =
                                        attractionsSavedList;
                                    _prefs.setString("Id", json.encode(newMap));
                                  }
                                } else {
                                  if (attractionsSavedList.length > 0) {
                                    Map newMap = Map();
                                    newMap[widget.cities.city] =
                                        attractionsSavedList;
                                    _prefs.setString("Id", json.encode(newMap));
                                  }
                                }

                                /*  {
                                  widget.cities.city: attractionsSavedList
                                }; */

                                attractionsSavedList.forEach((element) {
                                  print(
                                      'This is the list after adding  $element');
                                });
                              }
                            });
                          },
                          icon: Icon(
                            attractionsExists ? Icons.star : Icons.star_border,
                            color:
                                attractionsExists ? Colors.blue : Colors.blue,
                          ),
                        )
                      ],
                    ),
                  ));
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class SavedAttractions extends StatefulWidget {
  @override
  _SavedAttractionsState createState() => _SavedAttractionsState();
}

class _SavedAttractionsState extends State<SavedAttractions> {
  List<String> attractionsList = List();
  bool _isLoading = false;

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

  getSavedAttractions() async {
    setState(() {
      _isLoading = true;
    });
    SharedPreferences preferences = await SharedPreferences.getInstance();

    String value = preferences.getString('Id');

    Map<String, dynamic> newMapVAlue = json.decode(value);

    newMapVAlue.forEach((key, value) {
      value.forEach((elements) {
        attractionsList.add(elements);
      });
    });

    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _isLoading
          ? Center(
              child: CircularProgressIndicator(),
            )
          : ListView.builder(
              itemCount: attractionsList.length,
              shrinkWrap: true,
              itemBuilder: (context, index) {
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(attractionsList[index]),
                  ),
                );
              }),
    );
  }
}

List<Cities> citiesFromJson(String str) =>
    List<Cities>.from(json.decode(str).map((x) => Cities.fromJson(x)));
String citiesToJson(List<Cities> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Cities {
  Cities({
    this.city,
    this.attractions,
  });

  String city;
  List<String> attractions;

  factory Cities.fromJson(Map<String, dynamic> json) => Cities(
        city: json["city"],
        attractions: List<String>.from(json["attractions"].map((x) => x)),
      );

  Map<String, dynamic> toJson() => {
        "city": city,
        "attractions": List<dynamic>.from(attractions.map((x) => x)),
      };
}


推荐阅读