Menu
CoddyTech

A fromJson Constructor

Part of the Data and the Network section of Coddy's Flutter journey. Lesson 17 of 27.

Reading user['name'] as String all over your screens gets messy, and one typo in a key breaks it. The usual fix is a class for the data, with one constructor that knows the JSON. It is called fromJson:

class User {
  User({required this.name, required this.age});

  final String name;
  final int age;

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      age: json['age'] as int,
    );
  }
}

User.fromJson is a named constructor, a second way to make a User. The word factory lets a constructor run code and return the object it builds. A plain constructor cannot return anything.

Now the JSON is read in one place. The rest of the app gets a real User, where user.name is a String and a typo is a compile error instead of a null:

final body = await fetch('/user');
return User.fromJson(jsonDecode(body));

Almost every app that talks to a server has a fromJson for each kind of data it receives.

challenge icon

Challenge

Hard

The profile says Unknown: loadUser ignores the JSON. Give User a factory User.fromJson(Map<String, dynamic> json) that reads name, email and age, and make loadUser return User.fromJson(jsonDecode(body)). Import dart:convert.

Try it yourself

import 'package:flutter/material.dart';

import 'api.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ProfileScreen(),
    );
  }
}

class ProfileScreen extends StatefulWidget {
  const ProfileScreen({super.key});

  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  late Future<User> _user;

  @override
  void initState() {
    super.initState();
    _user = loadUser();
  }

  Future<User> loadUser() async {
    final body = await fetch('/user');
    print(body);
    return User(name: 'Unknown', email: 'unknown', age: 0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      body: Center(
        child: FutureBuilder<User>(
          future: _user,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Text('Could not load: ${snapshot.error}');
            }
            if (snapshot.hasData) {
              final user = snapshot.data!;
              return Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                    user.name,
                    style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Text(user.email, style: const TextStyle(fontSize: 18)),
                  const SizedBox(height: 8),
                  Text('${user.age} years old'),
                ],
              );
            }
            return const CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

class User {
  User({required this.name, required this.email, required this.age});

  final String name;
  final String email;
  final int age;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Data and the Network

Practice on your own: Online Flutter compiler