Search code examples
flutterdartsortingasynchronousflutter-dependencies

How to sort list based on distance from current user returned by an async function in flutter?


I've been trying to sort a list of objects, Here's there code:-

class Locations {
  final GeoPoint location;
  Locations({this.location});
}

class LocationCard extends StatefulWidget {
  final int index;
  LocationCard({this.index});
  @override
  _LocationCardState createState() => _LocationCardState();
}

class _LocationCardState extends State<LocationCard> {
  @override
  Widget build(BuildContext context) {
    final List<Locations> locationlist = [
      Locations(location: GeoPoint(1, 2)),
      Locations(location: GeoPoint(2, 2)),
      Locations(location: GeoPoint(3, 2)),
      Locations(location: GeoPoint(4, 1))
    ]; //list to be sorted
    GeoPoint mylocation = GeoPoint(2, 2);
    double distance;
    var userindex;
    List sorted = [];

    //sort locationlist here

    for (var i = 0; i < sorted.length; i++) {
      if (sorted[i].location == mylocation) {
        userindex = sorted.indexOf(locationlist[i]);
      }
    }//gets user's index in list 

    futureconvert() async {
      distance = await Geolocator().distanceBetween(
              sorted[userindex].location.longitude,
              sorted[userindex].location.latitude,
              sorted[widget.index].location.longitude,
              sorted[widget.index].location.latitude) /
          1000;
      return distance;
    }

    return Container(
      child: Card(
        child: Center(
          child: FutureBuilder(
              future: futureconvert(),
              builder: (context, snapshot) {
                return Text(distance.toString());
              }),
        ),
      ),
    );
  }
}

class LocationList extends StatefulWidget {
  @override
  _LocationListState createState() => _LocationListState();
}

class _LocationListState extends State<LocationList> {
  @override
  Widget build(BuildContext context) {
    final List<Locations> locationlist = [
      Locations(location: GeoPoint(1, 2)),
      Locations(location: GeoPoint(2, 2)),
      Locations(location: GeoPoint(3, 2)),
      Locations(location: GeoPoint(4, 1))
    ]; //list to be sorted
    GeoPoint mylocation = GeoPoint(2, 2);//current user location

    List sorted = [];

    //sort locationlist here 

    return ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: sorted.length,
        itemBuilder: (context, index) {
          return LocationCard(index: index);
        });
  }
}

I need a function that sorts locationlist base on proximity to mylocation.I tried sorting using value returned by this function

distancef(var x, var y) async {
      return await Geolocator().distanceBetween(
          locationlist[userindex].location.longitude,
          locationlist[userindex].location.latitude,
          x,
          y);
    }

But it look's like I have to write my own custom sorting function because I have a future value in return.

How do I do this?


Solution

  • Since we need the distance anyway, we can map each location to distance first. Then sort them by distance. A FutureBuilder can be used to build the ListView.

    class LocationList extends StatefulWidget {
      @override
      _LocationListState createState() => _LocationListState();
    }
    
    class _LocationListState extends State<LocationList> {
      @override
      Widget build(BuildContext context) {
        // location list and mylocation
    
        Future<int> distanceFromMyLocation(Locations location) async {
          int distance = await Geolocator().distanceBetween(
                  mylocation.location.longitude,
                  mylocation.location.latitude,
                  location.location.longitude,
                  location.location.latitude) /
              1000;
          return distance;
        }
    
        // Return a list of location and corresponding distance from user
        Future<List<Map<String, dynamic>>> sortByDistance(List<Locations> locationlist) async {
    
          // make this an empty list by intializing with []
          List<Map<String, dynamic>> locationListWithDistance = [];
      
          // associate location with distance
          for(var location in locationlist) {
            int distance = await distanceFromMyLocation(location);
            locationListWithDistance.add({
              'location': location,
              'distance': distance,
            });
          }
    
          // sort by distance
          locationListWithDistance.sort((a, b) {
            int d1 = a['distance'];
            int d2 = b['distance'];
            if (d1 > d2) return 1;
            else if (d1 < d2) return -1;
            else return 0;
          });
    
          return locationListWithDistance;
        }
    
        return FutureBuilder(
          future: sortByDistance(locationlist),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return CircularProgressIndicator();
            }
    
            var sorted = snapshot.data as List<Map<String, dynamic>>;
    
            return ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: sorted.length,
              itemBuilder: (context, index) {
                return LocationCard(sorted[index]);
              });
          },
        );
      }
    }
    
    class LocationCard extends StatelessWidget {
      final Map<String, dynamic> locationAndDistance;
      LocationCard({this.locationAndDistance});
      
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Card(
            child: Center(
              // location can be accessed by locationAndDistance['location']
              // distance can be accessed by locationAndDistance['distance']
              child: Text(locationAndDistance['distance'].toString());
            ),
          ),
        );
      }
    }