Flutter:将列表保存为收藏夹

Flutter: save list as favorite

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

这是型号 class :

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

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

有人可以帮忙吗

看看我做的例子。 以下是我添加的json 我只是更改了数据以供您理解

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

模型 class 来自您提供的 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和实现

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