Working directly with Document and DocumentList


Working directly with Document and DocumentList

TOC

In this part of the tutorial, we will take a look at using Document and DocumentList. So far in the example application in the tutorial, the user has been creating, editing, and deleting Documents with the UI that is automatically provided by Rapido for that purpose. In this part of the tutorial, we will be working with JSON we receive from a web service.

rick and morty

Working with JSON

Web API

The web service api in this example is the strangley alluring Rick and Morty API. It supports searching for characters, locations, and episodes, but this example will only use characters. It does not require an API key, so it makes the example easier. Also, I friggin’ love Rick and Morty.

Get JSON from the Web Service

There is a lot of boiler plate involved in retrieving JSON from the web and converting it to an object that Flutter can use. I wrote some simple, but not very robust code, that will return a Dart object that the app can use (assuming everything works, which is a terrible assumption to make).

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

Future<Map> getAndDecodeRemoteJSon(url, {Map<String, String> headers}) async {

  // create the http client
  http.Client client = http.Client();

  // create an untyped Map
  // if the JSON actually returns a list, this will blow up!
  Map object = {};

  // fetch the content from the url
  await client.get(url, headers: headers).then((http.Response response) async {

    // make the conversion and return the result
    await _decodeJson(response.body).then((result) {
      object = result;
    });
  }).whenComplete(client.close); //clean up

  return object;
}

// decode the json
Future _decodeJson(jsonBody) async {
  Map decoded = json.decode(jsonBody);
  return decoded;
}

None of this is particularly specific to Rapido so far.

There are different ways to create a search interface. I opted to grab the SearchBar package from pub. This creates a search bar in the title bar of the app.

Using the Decoded JSON

First, do some simple string formatting to create the URL for fetching the data:

    String url = "https://rickandmortyapi.com/api/character/?name=$value";

Then, we can use our get and decode code:

 getAndDecodeRemoteJSon(url).then((Map map) {
  /* do something with map */  
 }

This is where we finally get to the heart of the matter. At this point we will have a Map passed in. So now we have to figure out how to parse that Map. The easiest way to do that is to look at the actual json that gets returned. For our purposes, this is easy enough to do in a web browser, but here is the Json for a search:

{"info":{"count":1,"pages":1,"next":"","prev":""},"results":[{"id":259,"name":"Pencilvester","status":"Dead","species":"Alien","type":"Parasite, Pencil","gender":"Male","origin":{"name":"unknown","url":""},"location":{"name":"Earth (Replacement Dimension)","url":"https://rickandmortyapi.com/api/location/20"},"image":"https://rickandmortyapi.com/api/character/avatar/259.jpeg","episode":["https://rickandmortyapi.com/api/episode/15"],"url":"https://rickandmortyapi.com/api/character/259","created":"2017-12-31T13:33:48.488Z"}]}

As is typical, there is no formatting because the Json is intented for computer programs, not humans. In any case, we can see that there is Map in the form of Map<String, dynamic>. We want the results, which is a List. So we can extract that to work on it:

      List<dynamic> resultList = map["results"];

The we can iterate through the results:

      resultList.forEach((dynamic item) {
        /* do something with each result */
      });

Creating Documents

Finally, we are ready to create a Document with each search result. Note that it is typical to write some very specific code that parse out the API result, and make it work the way we want. In this example, we have to pull out “location” which you can see is actually another map, and then pull out the name of the location, which is the only part of the location object we care about:

  Document _docFromSearchResultsMap(Map<String, dynamic> map) {
    // extract the location, which is another map
    Map<String, dynamic> locOb = map["location"];

    // extract the name of the location (or leave it as null)
    String location;
    if (locOb != null) {
      location = locOb["name"];
    }
    // Create the Document with the desired values
    return Document(
      initialValues: {
        "name": map["name"],
        "Status": map["status"],
        "Species": map["species"],
        "Gender": map["gender"],
        "image": map["image"],
        "Origin": map["origin"],
        "Last Location": location,
      },
    );
  }

As an aside, note that a Document is built by providing a Map. You can create a document by passing a dart object directly. Rather, you must create a map to represent the object. For example, if you have an Event object, with say, a title property and a date property, you must convert this to a map before you pass it to the Document:

Map<String, dynamic> map = {"title":event.title, "date":event.date};
Document document = Document(initialValues: map);
}

Each time we create a Document, the search result is automatically saved on the user’s device. This is great because if they come back to the app after closing it, their results will still be there.

This also means that you can use the search results anywhere in your app by either passing along the DocumentList, or, you can create a new instance of the DocumentList with the same documentType, and it will read the results from disk. This can dramatically simplify your code base.

Creating the List

Because the Documents fit so naturally fit into a List, we will use a DocumentList to organize them.

Create a DocumentList along with a documentType string to keep track of the results.

  DocumentList _documentList = DocumentList("c137Results");

Now when we iterate through the results, we can add each Document to the DocumentList.

        resultList.forEach((dynamic item) {
          _documentList.add(_docFromSearchResultsMap(item));
        });

Clearing the Results

If the user does another search, we don’t want the old results hanging around. Because DocumentList is just a List with super powers, it is easy to clean up:

    _documentList.clear();

Note that this will delete all of the Documents in the DocumentList from the user’s device, which is what we want.

Displaying the Results

Now that you have a DocumentList filled with Documents, you can focus on displaying the results as you wish. You can see how I created the Charcter Card for an example, but I bet you can do better.

Summary

This section covered decoding JSON and using Document and DocumentList and easily present them.

Checkout the full example on Github.

There is also a video on the Youtube Channel that uses the same sample app to show some tips and tricks for enhancing the Ux with your DocumentListView.