flutterdart

Getting categories in Flutter News Toolkit


While trying to retrieve categories from the categories endpoint, I have ran into this error: _TypeError (type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>' in type cast)

This error is thrown by the return in the extension from the myprojectname_api_client.dart:

 Future<CategoriesResponse> getCategories() async {
    final uri = Uri.parse('$_baseUrl/categories');
    final response = await _httpClient.get(
      uri,
      headers: await _getRequestHeaders(),
    );
    final body = response.json();

    if (response.statusCode != HttpStatus.ok) {
      throw BernardinaiApiRequestFailure(
        body: body,
        statusCode: response.statusCode,
      );
    }

    return CategoriesResponse.fromJson(body);
  }

//------------

extension on http.Response {
  Map<String, dynamic> json() {
    try {
      final decodedBody = utf8.decode(bodyBytes);
      log("MyLog $body");
      return jsonDecode(decodedBody) as Map<String, dynamic>;
    } catch (error, stackTrace) {
      Error.throwWithStackTrace(
        BernardinaiApiMalformedResponse(error: error),
        stackTrace,
      );
    }
  }
}

The log shows that the decodeBody contains the full JSON from the categories endpoints.

The getCategories method in my custom class that implements NewsDataSource class looks like this:

Future<List<Category>> getCategories() async {
  final uri = Uri.parse('https://www.mynewsurl.com/wp-json/wp/v2/categories');
  final categoriesResponse = await http.get(uri);

  if (categoriesResponse.statusCode == HttpStatus.ok) {
    // Parse the response body into a Map
    final Map<String, dynamic> categoriesData = jsonDecode(categoriesResponse.body) as Map<String, dynamic>;
    
    // Access the 'categories' list from the map
    final List<dynamic> categoriesList = categoriesData['categories'] as List<dynamic>;
    log("MyLog categoriesList: $categoriesList");
    
    // Extract category names and return them as a list of Category enums
    final List<Category> categories = categoriesList.map((dynamic categoryData) {
      final String categoryName = categoryData as String; // Explicitly cast to String
      log("MyLog categoryName: $categoryName");

      return Category.fromString(categoryName); // Use fromString to get the enum
    }).toList();
    
    log("MyLog categories to return: $categories");

    return categories; // Returns list of category names
  } else {
    // Handle error cases by throwing a custom exception
    throw BernardinaiApiRequestFailure(
      statusCode: categoriesResponse.statusCode,
      body: jsonDecode(categoriesResponse.body) as Map<String, dynamic>,
    );
  }
}

///------

class CategoriesInfo {
  const CategoriesInfo(this.slug);

  final String slug;

  CategoriesInfo.fromJson(Map<String, dynamic> json)
      : slug = _isKebabCase(json['slug'] as String)
            ? _convertToCamelCase(json['slug'] as String)
            : json['slug'] as String;

  Map<String, dynamic> toJson() {
      log("mano as veikiu");
    return {
      'slug': slug,
    };
  }

  static bool _isKebabCase(String input) {
    log("MyLog checking what case is used");
    return input.contains('-');
  }

  static String _convertToCamelCase(String input) {
    log("MANO slugas: $input");
    List<String> parts = input.split('-');
    if (parts.length == 1) return input;

    String camelCase = parts[0];
    for (int i = 1; i < parts.length; i++) {
      camelCase += parts[i][0].toUpperCase() + parts[i].substring(1);
    }

    return camelCase;
  }
}

I tried to change the extension type to List, but then all the other methods are throwing the same error, so now I am wondering if the Future<List<Category>> getCategories() should not be a list but a map? But it was defined like that in the template, so maybe it's one of the bugs? Or perhaps I am not seeing something? I am completely lost and the documentation is of no help to me, so any help will be appreciated.


Solution

  • After analysing my code, I found that the problem was the logic I was trying to implement - the link inside the Future<CategoriesResponse> getCategories() should have been the link to the server that formats data to blocks that the app is expecting, and not to the JSON data source.