0

I have 2 JSON file:

country.json

[
  {
    "countryName": "United States",
    "countryContinent": "North America"
  },
  {
    "countryName": "Germany",
    "countryContinent": "Europe"
  },
  {
    "countryName": "United Kingdom",
    "countryContinent": "Europe"
  }
]

continent.json

[
  {
    "continentName": "North America",
    "continentCountry": [
      "Canada",
      "Mexico",
      "Cuba"
    ],
    "continentArea": 24790000,
    "continentFlag": [
      "https://www.countryflags.io/ca/shiny/64.png",
      "https://www.countryflags.io/mx/shiny/64.png",
      "https://www.countryflags.io/cu/shiny/64.png"
    ]
  },
  {
    "continentName": "Europe",
    "continentCountry": [
      "Denmark",
      "Finland",
      "France"
    ],
    "continentArea": 10180000,
    "continentFlag": [
      "https://www.countryflags.io/dk/shiny/64.png",
      "https://www.countryflags.io/fi/shiny/64.png",
      "https://www.countryflags.io/fr/shiny/64.png"
    ]
  }
]

I want to build a list based on country.json, then for each value of countryContinent that == value of continentName => show data of value continentCountry from continent.json like this

enter image description here

So pls help me, this is main file:

import 'package:ask/model/continent_model.dart';
import 'package:ask/model/country_model.dart';
import 'package:ask/services/continent_services.dart';
import 'package:ask/services/country_service.dart';
import 'package:flutter/material.dart';

class Demo2 extends StatefulWidget {
  Demo2() : super();
  @override
  _Demo2State createState() => _Demo2State();
}

class _Demo2State extends State<Demo2> {
  List<Country> _country = [];
  List<Continent> _continent = [];

  @override
  void initState() {
    super.initState();
    CountryServices.getCountry().then((countries) {
      setState(() {
        _country = countries;
      });
    });
    ContinentServices.getContinent().then((continents) {
      setState(() {
        _continent = continents;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Demo 2')),
        body: Column(
          children: <Widget>[
            for (Country country in _country)
              Row(
                children: <Widget>[
                  Expanded(child: Text(country.countryName)),
                  Expanded(child: Text(country.countryContinent)),
                  Expanded(child: Text('')), // How to show data of continentCountry
                ],
              )
          ],
        ));
  }
}


EDIT 2:

To avoid creating another post, I extend the following question: in continent.json, there is more data about continentCountry ("Greenland"; "Panama"; "Jamaica") that matches the value of continentName("North America") like this:

continent.json (edit 2)

[
  {
    "continentName": "North America",
    "continentArea": "Area1",
    "continentCountry": [
      "Canada",
      "Mexico",
      "Cuba"
    ],
    "continentFlag": [
      "https://www.countryflags.io/ca/shiny/64.png",
      "https://www.countryflags.io/mx/shiny/64.png",
      "https://www.countryflags.io/cu/shiny/64.png"
    ]
  },
  {
    "continentName": "North America",
    "continentArea": "Area2",
    "continentCountry": [
      "Greenland",
      "Panama",
      "Jamaica"
    ],
    "continentFlag": [
      "https://www.countryflags.io/gl/shiny/64.png",
      "https://www.countryflags.io/pa/shiny/64.png",
      "https://www.countryflags.io/jm/shiny/64.png"
    ]
  },
  {
    "continentName": "Europe",
    "continentArea": "Area3",
    "continentCountry": [
      "Denmark",
      "Finland",
      "France"
    ],
    "continentFlag": [
      "https://www.countryflags.io/dk/shiny/64.png",
      "https://www.countryflags.io/fi/shiny/64.png",
      "https://www.countryflags.io/fr/shiny/64.png"
    ]
  },
  {
    "continentName": "Asia",
    "continentArea": "Area4",
    "continentCountry": [
      "Japan"
    ],
    "continentFlag": [
      "https://www.countryflags.io/jp/shiny/64.png"
    ]
  }
]

so I want to display all of the continentCountry of "North America" as follows: enter image description here

Pls help

Kel
  • 453
  • 1
  • 11
  • 29
  • Hi @Kel, can you share your model and service file to me, I have the similar problem to show the data as in https://stackoverflow.com/questions/73732881/flutter-how-to-join-or-link-two-classes-in-a-json-file but not yet resolve – Joven Dev Sep 19 '22 at 06:40

2 Answers2

1

you can loop over the list of strings List<String> in an seperate function like List<Text> getSameContinentCountries.

class for data representation

Country

class Country {
  String countryName;
  String countryContinent;

  Country({this.countryName, this.countryContinent});

  factory Country.fromJson(Map<String, dynamic> json) {
    return Country(
      countryName: json['countryName'],
      countryContinent: json['countryContinent'],
    );
  }
}

Continent

class Continent {
  String continentName;
  List<String> continentCountry;

  Continent({this.continentName, this.continentCountry});

  factory Continent.fromJson(Map<String, dynamic> json) {
    return Continent(
      continentName: json['continentName'],
      continentCountry: json['continentCountry'],
    );
  }
}

You can now generate the List<Country> _country and List<Continent> _continent in the initState with:

// TODO generate Lists
    for (Map<String, dynamic> _json in continentListResponse) {
      continent2 = Continent.fromJson(_json);
      this._continent.add(continent2);
    }
    for (Map<String, dynamic> _json in countryListResponse) {
      country2 = Country.fromJson(_json);
      this._country.add(country2);
    }

function for Countries in Continents

Text only

List<Text> getSameContinentCountries({@required String countryContinent}) {
    int continentIndex = this
        ._continent
        .indexWhere((continent) => continent.continentName == countryContinent);
    List<String> _cC = this._continent[continentIndex].continentCountry;
    List<Text> wrapText = [Text('no Countries found')];
    if (_cC.length > 0) {
      wrapText = [];
      for (String country in _cC) {
        wrapText.add(Text(country));
      }
    }
    return wrapText;
  }

all widgets

List<Widget> getSameContinentCountries({@required String countryContinent}) {
    // returns a List of any Widget, many combinations possible
    // such as chips, cards etc.
    int continentIndex = this
        ._continent
        .indexWhere((continent) => continent.continentName == countryContinent);
    List<String> _cC = this._continent[continentIndex].continentCountry;
    List<Widget> wrapText = [Text('no Countries found')];
    if (_cC.length > 0) {
      wrapText = [];
      for (String country in _cC) {
        // you may want to use Padding
        wrapText.add(Padding(
          padding: const EdgeInsets.symmetric(horizontal: 2),
          child: Text(country),
        ));
        // simple | divider with Text widget
        wrapText.add(Padding(
          padding: const EdgeInsets.symmetric(horizontal: 2),
          child: Text('|'),
        ));
        // or use the VerticalDivider which needs a parent with height
        wrapText.add(
          Container(
              height: 15,
              child: VerticalDivider(
                color: Colors.red,
                width: 4,
                thickness: 2,
              )),
        );
        // or use any other widget, eg. chips
        wrapText.add(Chip(
          label: Text(country),
        ));
      }
    }
    // remove the last "divider"
    wrapText.removeLast();
    return wrapText;
  }

use function generated widget

Here I would suggest you to use the Wrap Widget.

@override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Demo 2')),
        body: Column(
          children: <Widget>[
            for (Country country in _country)
              Row(
                children: <Widget>[
                  Expanded(child: Text(country.countryName)),
                  Expanded(child: Text(country.countryContinent)),
                  Expanded(
                    child: Wrap(
                        children: getSameContinentCountries(
                            countryContinent: country.countryContinent)),
                  ),
                ],
              )
          ],
        ));
  }

Link to the result Image

UPDATE

I can't comment on another answer yet, so I have to include this short info in my answer.

To use the images, just return an Image widget, you may want to wrap it in a Sized Box or an Button / GestureDetctor for click events.

List<Widget> getFlags({@required String countryContinent}) {
    // returns a List of any Widget, many combinations possible
    // such as chips, cards, images, buttons etc.
    int continentIndex = this
        ._continent
        .indexWhere((continent) => continent.continentName == countryContinent);
    List<String> _cF = this._continent[continentIndex].continentFlag;
    List<Widget> wrapWidget = [Text('no Countries found')];
    if (_cF.length > 0) {
      wrapWidget = [];
      for (String flag in _cF) {
        // add the Flags
        wrapWidget.add(Image.network(flag));
      }
    }
    return wrapWidget;
  }

Result:

Flags

UPDATE 2

To have access to all flags with the same continentName, you have to extract the matching elemtens and then loop over the new List<Continent>. Change the code of the getFlags function like so:

List<Widget> getFlags({@required String countryContinent}) {
  // returns a List of any Widget, many combinations possible
  // such as chips, cards, images, buttons etc.
  //! UPDATE
  List<Continent> sameContient = this
      ._continent
      .where((continent) => continent.continentName == countryContinent)
      .toList();
  List<String> _cF = [];
  for (Continent sc in sameContient) {
    for (String flag in sc.continentFlag) {
      _cF.add(flag);
    }
  }
  List<Widget> wrapWidget = [Text('no Countries found')];
  if (_cF.length > 0) {
    wrapWidget = [];
    for (String flag in _cF) {
      // add the Flags
      wrapWidget.add(
          Image.network(flag, height: 16, width: 25, fit: BoxFit.contain));
    }
  }
  return wrapWidget;
}

and you will get the following result: enter image description here

SeriForte
  • 630
  • 7
  • 13
  • It works! Thanks, xD, one question: how to add " ," or " | " between each `continentCountry` – Kel Jun 11 '20 at 16:57
  • You could add a Text(',') in each cycle and remove the last one, or you cloud generate a List of Widgets `List` instead a List of Text `List` and include any other Widget you want. I Updated the Answer, hope it helps – SeriForte Jun 11 '20 at 18:03
  • @Kel: I Updated my answer for your Flag question – SeriForte Jun 12 '20 at 07:26
  • until now, I can fully understand your code, the way you make it can be customized a lot, and I also learned a lot about it. Thanks for your help xD – Kel Jun 12 '20 at 12:57
  • I edited the post for further ask, it's great If you can help me more – Kel Jun 16 '20 at 12:43
  • @Kel You can change the code for the `flag` like shown in the updated code. – SeriForte Jun 16 '20 at 15:49
  • One more question of Edit2, How can I show `String continentArea` (Json updated) together with `List continentFlag` by Row or Column like [this](https://i.imgur.com/TNk5VZk.jpg) pls help me – Kel Jun 19 '20 at 09:44
1
Expanded(
   child: MyCountries(
     continent: List<Continent>.from(_continent)..retainWhere((continent) => 
       continent.continentName == country.countryContinent)
),

I create a widget MyCountries that receives a List, first I create a copy of that list (List.from) to avoid changing the original list _continent. RetainWhere keeps only the elements that sastisfy the condition. You wanted to keep only the continent where countryContinent == continentName

continent.continentName == country.countryContinent

and in the Mycountries widget

class MyCountries extends StatelessWidget{
  final String countries;


  MyCountries({List<Continent> continent}) :
    this.countries = continent.reduce((prev, curr) => 
     prev..continentCountry.addAll(curr.continentCountry)).continentCountry.join(' | ');

  @override
  Widget build(BuildContext context) {
    return Text(countries);
  }
}

it gets the continent List and reduce it to a single continent object with a continentCountry List with all the countries, then just apply continentCountry.join(' | ') to join all the Strings of that list and create your countries String that you will use in the Text widget

UPDATE

From what I understand of your comments there should only be one ocurrence countryContinent that == value of continentName, I thought there could be more (my bad), so maybe you should change it like this

Expanded(
 child: MyArea(
 continent: _continent.firstWhere((continent) =>  continent.continentName == country.countryContinent)
 //This should return the first ocurrence, only one continent object
),

class MyArea extends StatelessWidget{
  final String area;

  MyArea({Continent continent}) :
    this.area = continent.continentArea.toString(); //You need to give a string to the Text widget anyway

  @override
  Widget build(BuildContext context) {
    return Text(area);
  }
}

I suppose List is a list of urls of the flags

Expanded(
 child: MyFlags(
 continent: _continent.firstWhere((continent) =>  continent.continentName == country.countryContinent)
 //This should return the first ocurrence, only one continent object
),

class MyFlags extends StatelessWidget{
  final List<String> flags;

  MyFlags ({Continent continent}) :
    this.flags= continent.continentFlag;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly, //or try other alignments or using Spacer() between widget in children
      children: [
        for(String flag in flags)
         Image.network(flag, height: 16, width: 25, fit: BoxFit.contain)
         //A flag is wider thatn taller, but you could try other sizes if you want 
      ]
    );
  }
}

UPDATE 2

Expanded(
 child: MyFlags(
 continent: List<Continent>.from(_continent)..retainWhere((continent) => 
       continent.continentName == country.countryContinent)
),

class MyFlags extends StatelessWidget{
  final List<String> flags;
  final List<String> countries;

  MyFlags ({List<Continent> continent}) :
    this.flags = continents.expand<String>((continent) => continent.continentFlag).toList(),
    this.countries = continents.expand<String>((continent) => continent.continentCountry).toList(),
    assert(flags.length == countries.length); //They should have the same length

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly, //or try other alignments or using Spacer() between widget in children
      children: [
        for(int i = 0; i < flags.length; i++)
         Column(
           children: [
             Image.network(flags[i], height: 16, width: 25, fit: BoxFit.contain),
             Text(countries[i])
           ]
         )
      ]
    );
  }
}

UPDATE 3

Expanded(
 child: MyFlags(
 continent: List<Continent>.from(_continent)..retainWhere((continent) => 
       continent.continentName == country.countryContinent)
),

class MyFlags extends StatelessWidget{
  final List<Continent> continent;

  MyFlags({this.continent});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        for(int i = 0; i < continent.length; i++)
          Row(
            children: [
              Text(continent[i].continentArea.toString()), // or 'Area ${i+1}' if you want to show Area 1, Area 2, etc
              for(String flag in continent[i].continentFlag)
                Image.network(flags[i], height: 16, width: 25, fit: BoxFit.contain),
            ]
          )
      ]
    );
  }
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • Hi again, you always saved my life, Thank you xD – Kel Jun 11 '20 at 17:09
  • In case I want to show int "continentArea" (updated JSON in the post), what should I have to write [here](https://i.imgur.com/7MkBZdv.jpg) – Kel Jun 11 '20 at 18:27
  • Add it as a list of strings continentArea? – EdwynZN Jun 11 '20 at 19:23
  • Yepppppppppp :D – Kel Jun 12 '20 at 01:28
  • mmm I don't fully understand, areaCode is an int but you have a List, I don't think I can combine the int because that will give you just a sum of the codes, you could try changing this.area = map((code) => code.area).toList().join(' | '), and making this.area a String – EdwynZN Jun 12 '20 at 03:45
  • I mean how to show like [this](https://i.imgur.com/LHAqzIP.jpg) – Kel Jun 12 '20 at 03:58
  • check my update, is that what were you looking for? – EdwynZN Jun 12 '20 at 04:13
  • One more case pls, if I want to show `Image.network` of List `continentFlag` like [this](https://i.imgur.com/L7oZgvK.jpg) (Json updated), what is the code for that :D – Kel Jun 12 '20 at 05:54
  • I made some assumptions with the size of the flags and how do you want them spaced, but that is the basic to display them, you can wrap them with FittedBox or Expand, or try to change the boxfit – EdwynZN Jun 12 '20 at 15:14
  • Everything perfect!! Thank you :D – Kel Jun 13 '20 at 03:53
  • I edited the post for further ask, pls help me more – Kel Jun 16 '20 at 12:42
  • The first code before my update should work with what you want – EdwynZN Jun 16 '20 at 14:07
  • Ah, `.retainWhere` is incredible, but how about show `Image.network` of "continentFlag" like [this](https://i.imgur.com/nfp5N8o.jpg) (It seems that it's using `.firstwhere` so it doesn't show up at all) – Kel Jun 16 '20 at 15:11
  • combine the 2 codes, use retainWhere instead of firstwhere and in the class MyFlags pass the List continents and give this.flags = continents.expand((continent) => continent.continentFlag).toList(); – EdwynZN Jun 16 '20 at 16:14
  • Is it possible to display the `Column` of ("continentFlag","continentCouintry") like [this](https://i.imgur.com/yugTk8P.jpg)? or need to change the structure of json? – Kel Jun 17 '20 at 21:43
  • I updated my answer, take note it should work if and only if the json continentCountry and flags are ordered so the first element in the list of countries is for the first element in the list of flags – EdwynZN Jun 17 '20 at 23:02
  • Yeah, I realized that the question didn't make any sense, sorry about that, what I really wanted to ask was to show `String continentArea` (Json updated) together with` List continentFlag` by `Row` or `Column` like [this](https://i.imgur.com/TNk5VZk.jpg) pls help – Kel Jun 18 '20 at 00:05
  • I still have to say thank you very much again, your code is very concise, easy to learn and understand. Thanks for your patience in helping me from the basic to the complex of my questions xD – Kel Jun 19 '20 at 11:04
  • I have a new question [here](https://stackoverflow.com/questions/62540501/flutter-how-to-filter-data-from-json-based-on-tabbar-and-togglebuttons) but it hasn't been answered in 2 days :(. It is great if you can help me again. – Kel Jun 25 '20 at 20:03
  • I have a [new post](https://stackoverflow.com/questions/63220842/flutter-how-to-display-data-from-2-json-based-on-the-same-value-of-array) similar to this post, please help me xD – Kel Aug 02 '20 at 20:47
  • Hi @EdwynZN, can you help me with this https://stackoverflow.com/questions/73732881/flutter-how-to-join-or-link-two-classes-in-a-json-file – Joven Dev Sep 19 '22 at 06:51