GrandMagus
GrandMagus

Reputation: 742

Sorting a Future List in Flutter

I've been looking for a solution to sort a list (ascending and descending) On Button Press inside of a FutureBuilder, that is a Future<List>, but can't seem to understand how to define it as a List and then sort it on a button press. So I call the API, the API returns some dummy value, it's gets built in the Future Builder and in a ListView.builder, now I want sort the list by id (or by any type for that matter) but the method is not working because the list is null. The code:

API Call for the dummy data:

Future<List<Post>> fetchPosts() async {
  List<Post> posts = [];
  final response = await http.get('https://jsonplaceholder.typicode.com/posts');

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    var postsJson = jsonDecode(response.body);
    for (int i = 0; i < postsJson.length; i++) {
      posts.add(Post.fromJson(jsonDecode(response.body)[i]));
    }
    return posts;
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load posts');
  }
}

The Future Builder:

 List<Post> posts = []; /// if a define it like this, the value is always null
 Future<List<Post>> futurePosts;

  @override
  void initState() {
    super.initState();
    futurePosts = fetchPosts();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Column(
            children: [
              MaterialButton(color: Colors.grey, onPressed: (){
// here I am setting set to compare the values of all IDs so it can be sorted ascending and descending by number of ID every time I press the button
                setState(() {
                  posts.sort((a, b) => a.id.compareTo(b.id));
                });
              },),
              Container(
                height: 1000,
                child: FutureBuilder<List<Post>>(
                  future: futurePosts,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return ListView.builder(
                        shrinkWrap: true,
                        itemCount: snapshot.data.length,
                        itemBuilder: (context, index) {
                          return Text('${snapshot.data[index].id}')
                        },
                      );
                    } else if (snapshot.hasError) {
                      return Text("${snapshot.error}");
                    }
                    return Container();
                  },
                ),

But it seems my understanding and code is not working for me at this point. Any help is appreciated, thanks in advance!

Upvotes: 2

Views: 2754

Answers (2)

ChessMax
ChessMax

Reputation: 2265

Something like this, I think, should work:

List<Post> posts;

@override
void initState() {
  super.initState();
  fetchPosts().then((items) {
    setState(() {
      posts = items;
    });
  });
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.white,
    body: SafeArea(
      child: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: Column(children: [
          MaterialButton(
            color: Colors.grey,
            onPressed: () {
              setState(() {
                if (posts != null) {
                    posts = posts.toList();
                    posts.sort((a, b) => a.id.compareTo(b.id));
                }
              });
            },
          ),
          Container(
            height: 1000,
            child: (posts != null)
                ? ListView.builder(
                    shrinkWrap: true,
                    itemCount: posts.length,
                    itemBuilder: (context, index) {
                      return Text('${posts[index].id}');
                    },
                  )
                : Container(),
          )
        ]),
      ),
    ),
  );
}

Your posts field is always empty because you never assign data to that field. And this is the main problem. Try it out.

Upvotes: 0

Huthaifa Muayyad
Huthaifa Muayyad

Reputation: 12353

You can move your posts.sort((a, b) => a.id.compareTo(b.id)); inside your Future function, before returning posts. And change the setState, to change the state of a boolean, which sorts or not.

You can change like this:

//define a boolen
bool _isSorted =false;


Future<List<Post>> fetchPosts(bool sortORnot) async {
  List<Post> posts = [];
  final response = await http.get('https://jsonplaceholder.typicode.com/posts');

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    var postsJson = jsonDecode(response.body);
    for (int i = 0; i < postsJson.length; i++) {
      posts.add(Post.fromJson(jsonDecode(response.body)[i]));
    }
    if (sortORnot) {posts.sort((a, b) => a.id.compareTo(b.id));}// this will sort only if you wanted your list sorted.
    return posts;
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load posts');
  }
}

Change your FutureBuilder to this:

FutureBuilder<List<Post>>(
 future:_isSorted? fetchPosts(true):fetchPosts(false),
 builder: (context, snapshot) {

and setState to this:

 setState(() {
  _isSorted = !_isSorted; //this flips the value whenever you press it.
  });

Now, in your future builder, you should get the posts sorted, can you try this?

Upvotes: 1

Related Questions