Using Flutter to Create a Book Search App with Redux

Angga Dwi Arifandi
12 min readNov 13, 2019

--

In this article, i’ll try to explain how to implement Redux on your application to build a Book Search App. If you wanted to look directly at the code, it’s right below here.

And final result of the application that we want to build looks like this.

Application’s final result

Flutter is gaining more and more popularity over time since its release on December last year. More plugins published, more apps start migrating to Flutter, and more mobile devs start to glance their eyes towards Flutter. I already used Flutter for over a year now in my day-to-day job and side projects. Previously, came from an Android Developer background, i noticed a lot of challenge on developing mobile apps using Flutter. And one thing that i would like to bring up now is State Management.

State Management is one of the hot topics in Flutter Development. There are literally hundreds of article and talks that discuss about State Management alone. Why? I think this happens because Flutter uses Declarative UI Paradigm which differs from other mobile development framework, e.g. Native Android SDK. So if we’re planning on creating an application using Flutter for a long term, we need to really think about which State Management concept we’re going to use.

There are a lot of options that we can take right now. As Flutter’s popularity starts grow bigger, a lot of developer start experimenting on new state management options that they think will suit Flutter better. There are Inherited Widget, Bloc, Redux, MobX, and the newest one, Provider. I tried them all back and forth through my Flutter projects, and i have mixed feelings about them.

However, in this article i’ll try to explain how to implement Redux on your Flutter project. One reason why i chose to explain how to use redux is, because redux is a pretty popular state management concept that have already been implemented across several platforms, so i figured many people already got a grasp on how it works. Well, here it is.

If you’re not familiar yet with Redux but eager to know about it, i’ve written an article about it right here.

Right now, Redux already had some ported version of it in various language. Some of it are Kotlin, Swift, and Dart. Which are the language that builds popular apps on Android and iOS. Having used redux in my day to day job myself, i think why Redux is so popular is because it’s maintainable, testable, and there’s a lot of resources out there on how to use them. And most importantly, the community really backed the development on Redux itself, so we have some kind of “safety net”.

On Flutter itself, Redux gains some popularity among state management options. There are already some redux-related libraries on Dart’s pub, and two of the most important libraries are redux and flutter_redux both maintained by Brian Egan. Other than that, there’s also Redux Thunk, Flutter Redux Navigation, and Redux Persist Flutter which adds more functionality to Redux on Flutter, but more on that later. In this article we’ll be focusing on how to implement Redux’s main functionality on Flutter.

So let’s cut to the chase. In this article, we’ll create a Book Search App using Flutter with Redux. I think this app already covers most of the topic we should know before creating a Flutter app using Redux, which is:

  • Three main elements of Redux, action, state and reducer
  • How to use Middlewares
  • How to connect API and view layer
  • How to use Redux in our Flutter widgets

I hope four points above already cover the most important thing we must learn before we use Redux to build our apps. Of course there are still many points that aren’t covered on this article, if you had any suggestion on what topics should i write next don’t hesitate to write that on the comments section below.

Alright so before we start, be sure to open this repository which contains the exact code i’ll be referring to so you won’t get lost.

The application that we’re trying to make looks like this

Basically it’s just a plain search app that allows user to search their favourite books by its title. It’s a really really simple app. For debugging purpose, we also wanted to show current status of the App. There are four possible status that the app could achieve:

  • IDLE (when the app had just started and user haven’t do anything)
  • LOADING (when the app is making a HTTP call to the API)
  • SUCCESS (when the HTTP call succeed and the app starts showing result of the book)
  • FAILED (when the HTTP call failed for some reason)

After the user typed the book title they wanted to see and clicks SEARCH, the app would look like this:

And when it finally get the list of books matching current query, it looks like this.

Simple enough right? Alright let’s jump in to the code.

So first things first, i’ll explain the main structure of our project.

Main Structure of our Project

There are five folders (plus one generated folder) and one main file which contains the application launcher for our app. I’ll start by explaining each folder and what’s inside.

  • API

This folder contains necessary files to connect to the outside world, in this case it’s the openlibrary.org API.

  • Models

This folder contains files that will define necessary models we need for this app, which basically already defined by the API which we’re gonna get the data from.

  • Redux

This folder contains files that will define all Redux component needed for this app. Maybe you can even say that this folder was the brain of the apps, because all action the user will do would be processed on files inside this folder.

  • UI

This folder contains necessary UI elements to show user the books they’ve been searching.

  • Utils

This folder contains all miscellaneous files needed by the app, which doesn’t belong to other folders. For instance, a file that holds all needed constants for the app.

Now that we know what the app structure looks like, we’ll start by defining the models needed. In this project, i used json_annotation package from Dart to parse json response we get from the API so we don’t have to parse it ourself (we could though if we want).

So the API we’ll be getting our data from is http://openlibrary.org/search.json?q=QUERY, where QUERY is the title of book we’re looking for so it won’t be static. And the result we’re gonna get will look like this

And ‘docs’ object will contain array of document matching the query.

So with that in mind, there will be two files on the models folder, search_result.dart and document.dart. Search_result file will contain the main json we got from the API, whereas the document file will contain the individual document we got matching the query. The details of the file could be seen on Github. Moving on.

Next, we’ll create the API client file that will be responsible to handle API calls from the app. But, before we deal with the API layer, we need to define the constants file which will hold all of the constants needed for our app.

Our Constants.dart would look like this:

Now we had the url we’re getting the data from, we could continue to write our API Layer. We’ll be using Dart’s HTTP to make HTTP calls in this app.

There are two HTTP call we’ll make on this app,

  • Fetch Book Search result
  • Fetch Book Cover and Author Picture (we won’t be using Dart HTTP for this because we’ll use a library called cached_network_image to deal with image fetching)

So our HTTP client file would look like this

which basically contains one function to fetch the search result according to the query supplied.

Now that we’ve already seen three of our five folders (API, models, and utils). We’ll move on to the very core of the app, the Redux folder.

The redux folder contains five files:

We’ll start with the app_state.dart which contains state our application will hold.

So if we imagine, we need to hold two object on our application state.

  • SearchResult object, which will later contain our search results once it succeed making the API call.
  • SearchState object, which will contain current state of our app, which i already explained above.

Also, we need to define the initial state our application will get, when the app had just started and user haven’t done anything.

One last thing, we also need to create a copy function for our state, because if you remember correctly, you shouldn’t mutate the whole state object. Instead, what you need to do is you should just make the necessary change for the component intended (e.g. change the SearchResult only or SearchState only).

So the result of our file would look like this

Next up, we’ll write our actions.dart file which will hold possible actions that user can do inside our app.

We need to really think this through before start defining our actions. Number one lesson when defining Actions is, we don’t want to create actions that simply act as a simple setter (e.g. ChangeSearchStatus(newStatus) which will change the search status according to the parameters inside). Instead, we want to create Actions that defines, well… Actions.

So for example, instead of ChangeSearchStatus(newStatus), we want to define actions like DoSearch, CancelSearch. So the action would most likely to describe real actions that could be done towards the app.

In this case, we wanted to create three actions

  • DoSearch, which will be triggered once the SEARCH button is clicked
  • GotSearchResult, which will be triggered once the app got the search results from the API.
  • SearchFailed, which will be triggered once the API call fails for some reason. We also could make this action contains the cause of the failure, but i skipped it for simplicity sake.

Now that we defined our Actions, we could start writing our Reducer functions for each action.

I think it’s pretty straightforward what we wanted to do on reducer functions for each action

  • DoSearch Action

In this action, we wanted to change the current state to Loading. And we want to clear the current search results if it contains result from previous searches.

  • GotSearch Action

In this action, we wanted to change the current state to Success. And we want to pass the result we got from the API to our SearchResult object inside the state.

  • SearchFailed Action

In this action, we wanted to change the current state to Failed.

So with that being said, our reducer functions would look like this.

Next up, you see how our DoSearch action only changes the current state to Loading and clear the current search result. So where do we call our API? directly from the onClick callback inside the widget?

Well we could definitely do that and i bet that’ll also work. But, i prefer not to give too many responsibilities to the UI layer. I prefer UI layer to just call one function (or action), and that’s it. Why is it so? I’ll explain.

  • First, i’m afraid the code will get more cluttered faster if we throw too much responsibility to the UI layer (except for UI rendering, which is its original job).
  • Second, i’m afraid testing it would be harder if we do everything on the UI layer

So with that in mind, we should avoid calling the API layer directly from the UI layer. Then, the question persist, where do we call them from?

There are actually a few options that we could take here, we could create a Manager that observes every single state emitted and call the API from there, we could use a Presenter to make the API call. But the path that i will take is using the Redux’s built in middleware to do that.

So on our DoSearch action, we only change the state and clear the current search result, and what we should do after that? we need to call the API right and do actions based on the result. Well, middleware to the rescue! because middleware is a component that does exactly that.

So, what’s a Middleware exactly? Imagine that middleware is a middle man that stands between action and reducer. So basically every single thing that went from action to the reducer, went to middleware first. Just like the illustration below.

Can you imagine what we could do with that? Well we could do anything, we could cancel the action if it doesn’t suffice some condition, or in our case, we could call the API when the DoSearch action is being dispatched! Right?

So the UI will only have to call one action, which is DoSearch, but the app will still do two things, dispatch the DoSearch and also calls the API. Let’s just jump right in to the code

So as you can see, the definition of middleware above is inside the _doSearchMiddleware function, on the middleware above we call the next function which will dispatch the current action (DoSearch action), then calls the api client to start fetching results after dispatching them. Last, we will store either the GotSearchResults or SearchFailed action based on the result we got from the API.

And last, we need to define the Store object we wanted to create for this app. There are many ways to do this, but i went with the global variable approach, so i created the store.dart file and instantiate a store object there that can be accessed basically anywhere on the app. It would look like this

Relax, we’re almost there. We just went through the most complex part of our app. Now just the UI part left.

I created the search_result_widget.dart inside the UI folder to render the result we got from the API into a bunch of information like the book cover, book title, book author, etc.

Now let’s explore the main widget of the app, which lies inside main.dart

To be able to distribute the store object to every single component of our app, we need to wrap our MaterialApp widget inside the StoreProvider<AppState> widget with the store provided as a params.

With this widget supplied, to get the current store file from inside the widget, all we need to do is wrap our widget inside a StoreConnector<AppState,X> where X is basically the destined type you want to consume the app state in. For instance, a viewModel which you could consume the needed data for that screen. In this case, i created a _SearchScreenViewModel which contains the current state and current search result we got from the state

With that info we got from the state, we could dispatch the DoSearch action from inside the SEARCH button, we could show the search results based on current state & search results we get from the state, and shows the user current state of our app.

There you go! Now you can run the project and see a book search app that works like a charm! You could do book search and find detail information about the book. Congrats!

Now that our app is already finished…. Wait, you think we’re finished? Hold your breath there, There are still many ways we could make use out of Redux on our App that’s not covered on this tutorial. Maybe we would add more functionality as we learn more Redux principles & features, or maybe we could add unit tests to existing code. Sounds like fun, right? Stay tuned!

I hope my explanation is comprehensive enough for you to understand how to use Redux inside a Flutter app that needs to make an API call, etc. Hopefully you enjoy this article as much as i do, and don’t forget if you got any suggestions feel free to comment below.

See you on the next article!

Have a good day!

--

--

Angga Dwi Arifandi

A passionate Software Engineer who's curious about everything.