Chapters

Hide chapters

Flutter Apprentice

First Edition - Early Access 2 · Flutter 1.20 · Dart 2.9 · AS 4.0.1

Section III: Navigating Between Screens

Section 3: 3 chapters
Show chapters Hide chapters

Section V: Deployment

Section 5: 4 chapters
Show chapters Hide chapters

11. Serialization With JSON
Written by Kevin D Moore

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In this chapter, you’ll learn how to serialize JSON data into model classes. A model class represents an object that your app can manipulate, create, save and search. An example is a recipe model class, which usually has a title, an ingredient list and steps to cook it.

You’ll continue with the previous project, which is the starter project for this chapter, and you’ll add a class that models a recipe and its properties. Then you’ll integrate that class into the existing project.

By the end of the chapter, you’ll know:

  • How to serialize JSON into model classes.
  • How to use Dart tools to automate the generation of model classes from JSON.

What is JSON?

JSON, which stands for JavaScript Object Notation, is an open-standard format used on the web and in mobile clients. It’s the most widely used format for Representational State Transfer (REST)-based APIs that servers provide. If you talk to a server that has a REST API, it will most likely return data in a JSON format. An example of a JSON response looks something like this:

{
  "recipe": {
    "uri": "http://www.edamam.com/ontologies/edamam.owl#recipe_b79327d05b8e5b838ad6cfd9576b30b6",
    "label": "Chicken Vesuvio"
  }
}

That is an example recipe response that contains two fields inside a recipe object.

While it’s possible to treat the JSON as just a long string and try to parse out the data, it’s much easier to use a package that already knows how to do that. Flutter has a built-in package for decoding JSON, but in this chapter, you’ll use the json_serializable and json_annotation packages to help make the process easier.

Flutter’s built-in dart:convert package contains methods like json.decode and json.encode, which convert a JSON string to a Map<String, dynamic> and back. While this is a step ahead of manually parsing JSON, you’d still have to write extra code that takes that map and puts the values into a new class.

The json_serializable package comes in handy because it can generate model classes for you according to the annotations you provide via json_annotation. Before taking a look at automated serialization, you’ll see in the next section what manual serialization entails.

Writing the code yourself

So how do you go about writing code to serialize JSON yourself? Typical model classes have toJson() and fromJson() methods, so you’ll start with those.

class Recipe {
  final String uri;
  final String label;

  Recipe({this.uri, this.label});
}
factory Recipe.fromJson(Map<String, dynamic> json) {
  return Recipe(json['uri'] as String, json['label'] as String);
}

Map<String, dynamic> toJson() {
  return <String, dynamic>{ 'uri': uri, 'label': label}
}

Automating JSON serialization

Open the starter project in the projects folder. You’ll use two packages in this chapter: json_annotation and json_serializable from Google.

Adding the necessary dependencies

Add the following package to pubspec.yaml in the Flutter dependencies section underneath and aligned with flutter_statusbarcolor: ^0.2.3:

json_annotation: ^3.1.0
build_runner: ^1.10.0
json_serializable: ^3.5.0
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.0
  cached_network_image: ^2.3.2+1
  flutter_slidable: ^0.5.7
  flutter_svg: ^0.19.0
  shared_preferences: ">=0.5.8 <2.0.0"
  flutter_statusbarcolor: ^0.2.3
  json_annotation: ^3.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  build_runner: ^1.10.0
  json_serializable: ^3.5.0

Generating classes from JSON

The JSON that you’re trying to serialize looks something like:

{
  "q": "pasta",
  "from": 0,
  "to": 10,
  "more": true,
  "count": 33060,
  "hits": [
    {
      "recipe": {
        "uri": "http://www.edamam.com/ontologies/edamam.owl#recipe_09b4dbdf0c7244c462a4d2622d88958e",
        "label": "Pasta Frittata Recipe",
        "image": "https://www.edamam.com/web-img/5a5/5a5220b7a65c911a1480502ed0532b5c.jpg",
        "source": "Food Republic",
        "url": "http://www.foodrepublic.com/2012/01/21/pasta-frittata-recipe",
    }
  ]
}

Creating model classes

Start by creating a new directory named network in the lib folder. Inside this folder, create a new file named recipe_model.dart. Then add the needed imports:

import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'recipe_model.g.dart';
@JsonSerializable()
class APIRecipeQuery {
}
final bool nullable;

/// Creates a new [JsonSerializable] instance.
const JsonSerializable({
  this.anyMap,
  this.checked,
  this.createFactory,
  this.createToJson,
  this.disallowUnrecognizedKeys,
  this.explicitToJson,
  this.fieldRename,
  this.ignoreUnannotated,
  this.includeIfNull,
  this.nullable,
  this.genericArgumentFactories,
});

Converting to and from JSON

Now, return to recipe_model.dart. Add methods that will convert from JSON to APIRecipeQuery and back:

factory APIRecipeQuery.fromJson(Map<String, dynamic> json) => _$APIRecipeQueryFromJson(json);

Map<String, dynamic> toJson() => _$APIRecipeQueryToJson(this);
@JsonKey(name: 'q')
String query;
int from;
int to;
bool more;
int count;
List<APIHits> hits;
APIRecipeQuery({
  @required this.query,
  @required this.from,
  @required this.to,
  @required this.more,
  @required this.count,
  @required this.hits,
});
// 1
@JsonSerializable()
class APIHits {
  // 2
  APIRecipe recipe;

  // 3
  APIHits({
    @required this.recipe,
  });

  // 4
  factory APIHits.fromJson(Map<String, dynamic> json) => _$APIHitsFromJson(json);
  Map<String, dynamic> toJson() => _$APIHitsToJson(this);
}
@JsonSerializable()
class APIRecipe {
  // 1
  String label;
  String image;
  String url;
  // 2
  List<APIIngredients> ingredients;
  double calories;
  double totalWeight;
  double totalTime;

  APIRecipe({
    @required this.label,
    @required this.image,
    @required this.url,
    @required this.ingredients,
    @required this.calories,
    @required this.totalWeight,
    @required this.totalTime,
  });

  // 3
  factory APIRecipe.fromJson(Map<String, dynamic> json) => _$APIRecipeFromJson(json);
  Map<String, dynamic> toJson() => _$APIRecipeToJson(this);
}

// 4
String getCalories(double calories) {
  if (calories == null) {
    return "0 KCAL";
  }
  return calories.floor().toString() + ' KCAL';
}

// 5
String getWeight(double weight) {
  if (weight == null) {
    return '0g';
  }
  return weight.floor().toString() + 'g';
}
@JsonSerializable()
class APIIngredients {
  // 1
  @JsonKey(name: 'text')
  String name;
  double weight;

  APIIngredients({
    @required this.name,
    @required this.weight,
  });

  // 2
  factory APIIngredients.fromJson(Map<String, dynamic> json) => _$APIIngredientsFromJson(json);
  Map<String, dynamic> toJson() => _$APIIngredientsToJson(this);
}

Generating the .part file

Open the terminal in Android Studio by clicking on the panel in the lower left, or by selecting View ▸ Tool Windows ▸ Terminal, and type:

flutter pub run build_runner build
Precompiling executable...
Precompiled build_runner:build_runner.
[INFO] Generating build script...
...
[INFO] Creating build script snapshot......
...
[INFO] Running build...
...
[INFO] Succeeded after ...
flutter pub run build_runner watch
// 1
APIRecipeQuery _$APIRecipeQueryFromJson(Map<String, dynamic> json) {
  return APIRecipeQuery(
    // 2
    query: json['q'] as String,
    // 3
    from: json['from'] as int,
    to: json['to'] as int,
    more: json['more'] as bool,
    count: json['count'] as int,
    // 4
    hits: (json['hits'] as List)
        ?.map((e) =>
            e == null ? null : APIHits.fromJson(e as Map<String, dynamic>))
        ?.toList(),
  );
}

Testing the generated JSON code

Now that you have the ability to parse model objects from JSON, you’ll read one of the JSON files included in the starter project and show one card to make sure you can use the generated code.

import 'dart:convert';
import '../..//network/recipe_model.dart';
import 'package:flutter/services.dart';
import '../recipe_card.dart';
APIRecipeQuery _currentRecipes1;
Future loadRecipes() async {
  // 1
  var jsonString = await rootBundle.loadString('assets/recipes1.json');
  setState(() {
    // 2
    _currentRecipes1 = APIRecipeQuery.fromJson(jsonDecode(jsonString));
  });
}
@override
void initState() {
  super.initState();
  loadRecipes();
  // ... rest of method
}
Widget _buildRecipeCard(BuildContext context, List<APIHits> hits,
    int index) {
  // 1
  APIRecipe recipe = hits[index].recipe;
  return GestureDetector(
    onTap: () {
    },
    // 2
    child: recipeStringCard(recipe.image, recipe.label),
  );
}
Widget _buildRecipeLoader(BuildContext context) {
  // 1
  if (_currentRecipes1 == null || _currentRecipes1.hits == null) {
    return Container();
  }
  // Show a loading indicator while waiting for the movies
  return Center(
    // 2
    child: _buildRecipeCard(context, _currentRecipes1.hits, 0),
  );
}

Key Points

  • JSON is an open-standard format used on the web and in mobile clients, especially with REST APIs.
  • In mobile apps, JSON code is usually parsed into the model objects that your app will work with.
  • You can write JSON parsing code yourself, but it’s usually easier to let a JSON package generate the parsing code for you.
  • json_annotation and json_serializable are packages that will let you generate the parsing code.

Where to go from here?

In this chapter, you’ve learned how to create models that you can parse from JSON and then use when you fetch JSON data from the network. If you want to learn more about json_serializable, go to https://pub.dev/packages/json_serializable.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now