Auto Generate JSON Models in Flutter, Filtering a List and Delay Searching

By | July 28, 2019
Generate JSON Flutter

Generate JSON Flutter


Filter List Flutter

Filter List Flutter


In this article we will see how to automatically generate json models in Flutter.

Add Plugins

For this we need some plugins…

Open the pubspec.yaml file and add these dependencies.

If you want to use json annotation, you can have ‘json_annotation’ plugin as well.

dependencies:
  flutter: 
    sdk: flutter
    ...
    json_annotation:
 
dev_dependencies:
  flutter_test:
    sdk: flutter
  json_model:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

Below is the url that we are going to parse and create the models for.

https://jsonplaceholder.typicode.com/users

Here you can see 10 User records.

Let’s Start…


The first thing we have to do is to create a folder named ‘jsons‘ in the root of our project. This is the default folder, but you can have your own name. In that case, you need to specify the folder named while running the auto generate commands in the terminal.

Now create a new file named “user.json” in the ‘jsons‘ folder and copy the user record into it.

  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  }
  

Then go to terminal and run the following command.

  flutter packages pub run json_model
 

Now you should see a new folder named “models” inside the “lib” folder.

There you can see the autogenerated user model file.

So far so good…but its not over.

We have nested objects inside user object.

The “address” property is the nested object.

Create a new ‘address.json’ file inside ‘jsons’ folder and change the json in the users.json to below.

  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": "$address", // address is the file name
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  }
  

Run the command again and you should see the new generated files and User model is updated like below. Do the same thing for ‘geo’ and ‘company nested objects.



@JsonSerializable()
class User {
    User();

    num id;
    String name;
    String username;
    String email;
    Address address;
    String phone;
    String website;
    Company company;
    
    factory User.fromJson(Map<String,dynamic> json) => _$UserFromJson(json);
    Map<String, dynamic> toJson() => _$UserToJson(this);
}

One last one is we see that we have the list of users, so we need to generate a model for the ‘users’ array.

Create a new file ‘users.json’ and update it like this

{
    "users": "$[]user"
}

Running the command again should generate users.dart.

part 'users.g.dart';

@JsonSerializable()
class Users {
  Users();

  List<User> users;

  factory Users.fromJson(Map<String, dynamic> json) => _$UsersFromJson(json);
  Map<String, dynamic> toJson() => _$UsersToJson(this);

}

Parse the Url

Create a file named “Services.dart” and copy the below contents.

This will parse the response and get the users list.

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../models/user.dart';
import '../../models/users.dart';

class Services {
  static const String url = 'https://jsonplaceholder.typicode.com/users';

  static Future<Users> getUsers() async {
    try {
      final response = await http.get(url);
      if (200 == response.statusCode) {
        return parseUsers(response.body);
      } else {
        return Users();
      }
    } catch (e) {
      print('Error ${e.toString()}');
      return Users();
    }
  }

  static Users parseUsers(String responseBody) {
    final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
    List<User> users = parsed.map<User>((json) => User.fromJson(json)).toList();
    Users u = Users();
    u.users = users;
    return u;
  }
}

Now We will create a list and add a TextField to Search through the list.


Add the Delay for Searching

We want to search the list when the user types in the TextField, but we don’t want to do it in every key stroke, instead we will wait for the user to stop typing and search.

For that we will write a separate class.

In this class, we will use a timer to cancel the search and start the search.

class Debouncer {
  final int milliseconds;
  VoidCallback action;
  Timer _timer;

  Debouncer({this.milliseconds});

  run(VoidCallback action) {
    if (null != _timer) {
      _timer.cancel();
    }
    _timer = Timer(Duration(milliseconds: milliseconds), action);
  }
}

Below is the function we use to search through the list. You can call this function in the onChange event of the TextField.

static Users filterList(Users users, String filterString) {
    Users tempUsers = users;
    List<User> _users = tempUsers.users
        .where((u) =>
            (u.name.toLowerCase().contains(filterString.toLowerCase())) ||
            (u.email.toLowerCase().contains(filterString.toLowerCase())))
        .toList();
    users.users = _users;
    return users;
}

Complete Code

Here is the complete UI code.

import 'package:flutter/material.dart';
import 'dart:async';
import 'Services.dart';
import '../../models/users.dart';

class UserListDemo extends StatefulWidget {
  UserListDemo() : super();

  final String title = "Filter List Demo";

  @override
  UserListDemoState createState() => UserListDemoState();
}

class Debouncer {
  final int milliseconds;
  VoidCallback action;
  Timer _timer;

  Debouncer({this.milliseconds});

  run(VoidCallback action) {
    if (null != _timer) {
      _timer.cancel();
    }
    _timer = Timer(Duration(milliseconds: milliseconds), action);
  }
}

class UserListDemoState extends State<UserListDemo> {
  //
  final debouncer = Debouncer(milliseconds: 1000);
  Users users;
  String title;

  @override
  void initState() {
    super.initState();
    title = 'Loading users...';
    users = Users();
    Services.getUsers().then((usersFromServer) {
      setState(() {
        users = usersFromServer;
        title = widget.title;
      });
    });
  }

  Widget list() {
    return Expanded(
      child: ListView.builder(
        itemCount: users.users == null ? 0 : users.users.length,
        itemBuilder: (BuildContext context, int index) {
          return row(index);
        },
      ),
    );
  }

  Widget row(int index) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(10.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              users.users[index].name,
              style: TextStyle(
                fontSize: 16.0,
                color: Colors.black,
              ),
            ),
            SizedBox(
              height: 5.0,
            ),
            Text(
              users.users[index].email.toLowerCase(),
              style: TextStyle(
                fontSize: 14.0,
                color: Colors.grey,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget searchTF() {
    return TextField(
      decoration: InputDecoration(
        border: OutlineInputBorder(
          borderRadius: const BorderRadius.all(
            const Radius.circular(
              5.0,
            ),
          ),
        ),
        filled: true,
        fillColor: Colors.white60,
        contentPadding: EdgeInsets.all(15.0),
        hintText: 'Filter by name or email',
      ),
      onChanged: (string) {
        debouncer.run(() {
          setState(() {
            title = 'Searching...';
          });
          Services.getUsers().then((usersFromServer) {
            setState(() {
              users = Users.filterList(usersFromServer, string);
              title = widget.title;
            });
          });
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Container(
        padding: EdgeInsets.all(10.0),
        child: Column(
          children: <Widget>[
            searchTF(),
            SizedBox(
              height: 10.0,
            ),
            list(),
          ],
        ),
      ),
    );
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *