How to create your own Lord of the Rings app with Flutter and The One API

Moritz Fink
6 min readSep 14, 2022

--

Ever wanted to create a The Lord of the Rings app but did not know how to start? Then this article will help you in finding the right approach by outlining how to set up a Flutter app and fetch basic LOTR information from The One API using the lotr_api package .

0. Prerequisites

In order to complete this tutorial, you will need to install the Flutter SDK and an IDE of your choice. Just follow the official instructions here: https://docs.flutter.dev/ .

Note: We will use IntelliJ in this article, but any other IDE with a Flutter/Dart plugin should be fine as well.

1. Create a new project

First of all, we create a new Flutter project (e.g. in IntelliJ via File > New > Project):

“There was Eru, the One, who in Arda is called Ilúvatar; and he made first the Ainur, the Holy Ones, that were the offspring of his thought, and they were with him before aught else was made.”

2. Books page

Let’s start by creating a simple page where we will display all LOTR books. First we add a file books_page.dart to the lib folder with following contents:

import 'package:flutter/material.dart';

class BooksPage extends StatefulWidget {
const BooksPage({Key? key}) : super(key: key);

@override
State<BooksPage> createState() => _BooksPageState();
}

class _BooksPageState extends State<BooksPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Books'),
),
body: const Center(
child: Text('Loading...'),
),
);
}
}

Then we reference that page in main.dart:

import 'package:flutter/material.dart';
import 'package:the_one_app/books_page.dart';

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

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

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'The One App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const BooksPage(),
);
}
}

When we now run the app, we simply get one screen with a static Loading… text. In order to fix that we need to get some data from The One API . Normally we would have to make some http calls and create data objects first, but luckily this has all been previously done by someone else, so we may just use the lotr_api package .

Add the package to your app by executing the following command in the terminal

#> flutter pub add lotr_api

or add the dependency directly to your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter

cupertino_icons: ^1.0.2
lotr_api: ^0.0.1

Do not forget to pull the new dependency via

#> flutter pub get

Finally, we can extend our BooksPage as follows:

import 'package:flutter/material.dart';
import 'package:lotr_api/lotr_api.dart';

class BooksPage extends StatefulWidget {
const BooksPage({Key? key}) : super(key: key);

@override
State<BooksPage> createState() => _BooksPageState();
}

class _BooksPageState extends State<BooksPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Books'),
),
body: FutureBuilder(
future: LotrApi().getBooks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
List<Book> books = snapshot.data?.docs ?? [];
return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) => ListTile(
title: Text(books[index].name),
),
);
}
return const Center(
child: Text('Loading...'),
);
},
),

);
}
}

So there are a lot of things happening here:

  • We imported the lotr_api package
  • We wrapped the Loading… text inside a FutureBuilder, so that we can call the getBooks() method of the LotrApi() object which returns a Future containing a paginated list of books
  • As soon as the API call is done (i.e. the Future is loaded) we display a ListView with the individual book titles

“Books ought to have good endings. How would this do: and they all settled down and lived together happily ever after?”

3. Chapters page

Let’s go a bit further and allow the user to check out the chapters of each book. First we create a new page chapters_page.dart :

import 'package:flutter/material.dart';
import 'package:lotr_api/lotr_api.dart';

class ChaptersPage extends StatefulWidget {
final Book book;

const ChaptersPage({
Key? key,
required this.book,
}) : super(key: key);

@override
State<ChaptersPage> createState() => _ChaptersPageState();
}

class _ChaptersPageState extends State<ChaptersPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.book.name),
),
body: FutureBuilder(
future: LotrApi().getBookChapters(
bookId: widget.book.id,
),

builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
List<Chapter> chapters = snapshot.data?.docs ?? [];
return ListView.builder(
itemCount: chapters.length,
itemBuilder: (context, index) =>
ListTile(
title: Text(chapters[index].chapterName),
),
);
}
return const Center(
child: Text('Loading...'),
);
},
),
);
}
}

As you can see the only difference to the BooksPage is that the ChaptersPage requires a book parameter to display the correct book title and chapter names.

When the user clicks on the respective book list item on the BooksPage, we route to the ChaptersPage for that specific book:

return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) => ListTile(
title: Text(books[index].name),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChaptersPage(
book: books[index],
),
),
),

),
);

“They say it is the first step that costs the effort. I do not find it so. I am sure I could write unlimited ‘first chapters’. I have indeed written many.”

4. Further API calls with API key

So far we only covered the endpoints of the API which do not require an API key. If you also want to fetch data about movies, characters, etc., you must provide an API key like this:

LotrApi(
apiKey = 'INSERT_YOUR_API_KEY_HERE', // TODO
)

In order to get the API key, you need to sign up to The One API here: https://the-one-api.dev/sign-up . It is 100% free and takes less than a minute.

Once you include the API key in your code, you can call following methods:

  • getChapters()
  • getMovies()
  • getCharacters()
  • getQuotes()

I will leave it up to you (as an exercise 😉) to play around and create further pages. Alternatively, have a look at the full example app on GitHub .

“I don’t know half of you half as well as I should like; and I like less than half of you half as well as you deserve.”

5. Sorting, pagination & filters

Some API endpoints like characters and quotes offer a lot of data which you may want to handle already at the time of calling the API. Therefore each of the API’s methods offers several options:

5.1 Sorting

Use the sorting parameter to specify an attribute to sort your results by (either ascending or descending). E.g. if you want to sort all quotes by dialog in ascending order, write:

Response<Quote> quotes = await lotrApi.getQuotes(
sorting: QuoteSortings.byDialogAsc,
);

5.2 Pagination

Responses can be paginated using the pagination parameter. E.g. the following code returns only the third page of quotes, where the first page starts at the third element and each page has a maximum of ten elements:

Response<Quote> quotes = await lotrApi.getQuotes(
pagination: Pagination(
limit: 10,
offset: 2,
page: 2,
),

);

Each response comes with the necessary information to help you navigate through the pages:

class Response<T> {
final List<T> docs;
final int total;
final int limit;
final int offset;
final int? page;
final int? pages;
...

5.3 Filters

There are also several filters that help you find the entries which you are looking for. You may combine as much as you like. E.g. the following code snippet returns all movies with a name that had a budget between 100 (included) and 250 (excluded) million dollars:

Response<Movie> response = await lotrApi.getMovies(
nameFilters: [
Exists(),
],
budgetInMillionsFilters: [
GreaterThanOrEquals(100),
LessThan(250),
],

);

Available filters are:

  • Matches / NotMatches
  • Includes / Excludes
  • Exists / NotExists
  • MatchesRegex / NotMatchesRegex
  • Equals / NotEquals
  • LessThan / GreaterThan
  • LessThanOrEquals / GreaterThanOrEquals

“May it be a light to you in dark places, when all other lights go out.”

6. Conclusion

In this article, we demonstrated how you can integrate The One API into your Flutter app using the lotr_api package and discussed advanced concepts like sorting, pagination and filters.

The code for this tutorial can be found on GitHub .

“All we have to decide is what to do with the time that is given us.”

--

--

Moritz Fink

Originating from the quantum realm (with a Master grade in Computational Science), I am working as a Software Developer with experience especially in C++ & Java