Can't convert JSON list to list of photos in flutter

Viewed 397

I'm learning flutter by making a simple photo gallery from unsplash service and trying to fetch a list of photos from https://api.unsplash.com/photos.

I'm getting the runtime error:

type 'Future<dynamic>' is not a subtype of type 'FutureOr<List<Photo>>'

Though if I print a list instead of returning it I get a list of instances of Photo in console. What's wrong with my code and how to do it right?

My photo model looks like this:

class Photo {
  final String id;
  final String title;
  final String username;
  final String url;

  Photo({this.id, this.title, this.username, this.url});

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
        id: json['id'],
        title: json['alt_description'],
        username: json['user']['name'],
        url: json['urls']['raw']);
  }
}

My api data source:

import 'package:http/http.dart' as http;
import 'dart:convert';

class UnsplashApi {
  final String _clientId =
      "84y4hgje4ac868c2646c0eddjhfedihdhff612b04c264f3374c97fff98ed253dc9";

  Future<String> _fetch(String url) async {
    try {
      url = buildUrl(url);
      var response = await http.get(url);
      if (response.statusCode == 200) {
        return response.body;
      }
    } catch (e) {
      print(e);
    }
  }

  Future<dynamic> fetchDataByUrl(String url) async {
    var data = await _fetch(url);
    try {
      return json.decode(data);
    } catch (e) {
      print('Bad content, could not decode JSON');
    }
  }

  String buildUrl(url) {
    return url.contains('?')
        ? url + '&client_id=$_clientId'
        : url + '?client_id=$_clientId';
  }
}

My repository contract (interface):

import 'package:unsplash/domain/models/photo.dart';

abstract class PhotosRepository {
  Future<List<Photo>> getPhotosList();
}

My Unsplash Photos repository:

import 'package:unsplash/domain/models/photo.dart';
import 'package:unsplash/domain/repositories/photos_repository.dart';
import 'package:unsplash/infrastructure/data_sources/unsplash_api.dart';

class UnsplashPhotosRepository implements PhotosRepository {
  final UnsplashApi _api;
  final String _url = 'https://api.unsplash.com/photos';

  UnsplashPhotosRepository(this._api);

  @override
  Future<List<Photo>> getPhotosList() async {
    var result = _api.fetchDataByUrl(_url).then((data) {
      var list = data?.map((el) => Photo.fromJson(el)).toList();
      return list;
    });
    return result;
  }
}

And My test:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:unsplash/infrastructure/data_sources/unsplash_api.dart';
import 'package:unsplash/infrastructure/repositories/unsplash_photos_repository.dart';

    void main() {
      group('Unsplash api', () {
        test('should get search results', () async {
          final api = UnsplashApi();
          var result = await UnsplashPhotosRepository(api).getPhotosList();
          print(result.runtimeType);
          expect(result.isEmpty, false);
        });
      });

}
1 Answers

The problem most likely comes from this line.

@override
  Future<List<Photo>> getPhotosList() async {
    var result = _api.fetchDataByUrl(_url).then((data) {
      var list = data?.map((el) => Photo.fromJson(el)).toList();
      return list;
    });
    return result;
  }

As I can see, the type of data that is in then(), is dynamic, and you wanna .map over it, so perhaps you may need to explicitly cast a type for it since it doesn't infer that.

So change this to

 @override
  Future<List<Photo>> getPhotosList() async {
    final dynamic result = await _api.fetchDataByUrl(_url);
    return (result as List<dynamic>).map((dynamic el) => Photo.fromJson(el as Map<String, dynamic>)).toList();
  }

so I polished your code, I hope this can help you.

abstract class PhotosRepository {
  Future<List<Photo>> getPhotosList();
}

class Photo {
  Photo({
    this.id,
    this.title,
    this.username,
    this.url,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      id: json['id'] as String,
      title: json['alt_description'] as String,
      username: json['user']['name'] as String,
      url: json['urls']['raw'] as String,
    );
  }

  final String id;
  final String title;
  final String username;
  final String url;

  @override
  String toString() {
    return '''$id, $title, $username, $url''';
  }
}

class UnsplashApi {
  final String _clientId = "84y4hgje4ac868c2646c0eddjhfedihdhff612b04c264f3374c97fff98ed253dc9";

  Future<String> _fetch(String url) async {
    try {
      url = buildUrl(url);
      final http.Response response = await http.get(url);
      if (response.statusCode == 200) {
        return response.body;
      }
      return null;
    } catch (e) {
      print(e);
      return null;
    }
  }

  Future<dynamic> fetchDataByUrl(String url) async {
    final String data = await _fetch(url);
    try {
      return json.decode(data);
    } catch (e) {
      print('Bad content, could not decode JSON $e');
    }
  }

  String buildUrl(String url) {
    return url.contains('?') ? '$url&client_id=$_clientId' : '$url?client_id=$_clientId';
  }
}

class UnsplashPhotosRepository implements PhotosRepository {
  UnsplashPhotosRepository(this._api);

  final UnsplashApi _api;
  final String _url = 'https://api.unsplash.com/photos';

  @override
  Future<List<Photo>> getPhotosList() async {
    final dynamic result = await _api.fetchDataByUrl(_url);
    return (result as List<dynamic>)?.map((dynamic el) => Photo.fromJson(el as Map<String, dynamic>))?.toList();
  }
}

and the result will be

[nV8K0uguyiw, man in green zip up jacket beside woman in black shirt, XPS, https://images.unsplash.com/photo-1593643946890-b5b85ade6451?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE1OTU4N30, YE_2tlbi-IM, woman in black and white striped long sleeve shirt wearing black framed eyeglasses, Hans Mendoza, https://images.unsplash.com/photo-1597982178640-358c9e03bfae?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE1OTU4N30, cHRDevKFDBw, green palm trees near city buildings during daytime, Cameron Venti, https://images.unsplash.com/photo-1597982087634-9884f03198ce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE1OTU4N30, -O0kKUPxDAM, green and blue lighted building during night time, ZQ Lee, https://images.unsplash.com/photo-1597991840620-cecdef61763b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE1OTU4N30, F_-Ehgu36_8, person holding red disposable cup with black straw, jo jo ◡̈, https://images.unsplash.com/photo-1597922650352-77f42b5b6571?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjE1OTU4N30, 3_SeMFFlNvU, woman in brown t-shirt and black shorts <…>

you may check the full code here as well

https://gist.github.com/mhadaily/d721979393dd031062d9e1674747762d

Related