Why Investing in Custom Inventory Management Software Pays Off

Why Investing in Custom Inventory Management Software Pays Off

When it comes to managing your company’s inventory, there is no doubt that you need tools that are flexible and responsive enough to handle the many moving pieces (pun intended) of your operation. Today, these inventory management tools can even be implemented through mobile devices. With the increased flexibility of these mobile solutions, all you have left to decide is which software best suits your business needs.

Weighing the Benefits

In inventory management, the choice of solution could mean the difference between keeping a well-balanced stock sheet, orders being placed/fulfilled in a timely manner, and happy customers, or frustrating losses to your business. Every company’s inventory is managed differently. Perhaps you have heard that an off-the-shelf solution works for the warehouse 5 minutes down the road, but that is no guarantee that it will improve your processes in the same way. A mobile solution that is tailor-made for your operations can make all aspects of management more streamlined. Does an off-the-shelf product integrate, or integrate well, with your current backend systems? Do people with different roles on your team have different access levels to the solution as needed? Are you overpaying for the solution by purchasing features in bulk, some of which you will never use? Are there any workarounds that the off-the-shelf solution inherently causes? If so, what are those costs?

A custom mobile solution doesn’t force your processes to fit around it. Instead, custom solutions fit seamlessly into the life/processes of your company. At a glance, here are some things to consider when making the decision between off-the-shelf or custom:

Off-the-shelf solution

Pros:

  • Lower cost (up front)
  • Quick deployment
  • Dev/Design, QA testing done by the vendor

Cons:

  • May have to change processes to incorporate the solution
  • High customization costs or no ability to customize
  • Slow to evolve

Custom solution

Pros:

  • Tailored to match and meet your business needs/processes
  • Changes and updates to the solution happen more quickly
  • Integrates with other backend systems more seamlessly, and allows data to integrate automatically

Cons:

  • Higher upfront costs
  • Requires developer sources
  • Requires business participation during the development cycle (design, development, testing, training, etc.)

Probably the most important question to ask in deciding on a solution for your business is this: Is the cost of a custom application justified in the long term by the increased productivity, efficiency, and therefore increased profit margin your business will see?

Taking the Plunge to Custom

The key to success; is to begin with the end in mind. If the goal is to grow your business, then each action taken in pursuit of that vision should be future-focused. In our opinion, this is where the tried and true phrase “investing in the future” really proves true.

Custom mobility can become an integral part of your growth when it’s built for your processes. Out-of-the-box products can paint in broad strokes, but success lies in the details of your carefully constructed workflows. From roadmap strategy to deployment, we can make mobile work for you.

Interested in knowing more about how we build custom inventory management apps? We’d love to talk to you!

Troy Rich

Troy Rich

Troy was born and raised in Richmond, VA and graduated from the prestigious Virginia Polytechnic Institute and State University. He has a passion for developing and maintaining relationships and discovering how Shockoe can drive positive change. When not at the office, Troy can be found watching a Liverpool FC match in the local pub, dining at the closest Chick-fil-a, or learning how to play a new musical instrument (currently the banjo)

How Mobile is Changing Warehouse Communication

How Mobile is Changing Warehouse Communication

With the rise of Amazon and it’s 2-day, next day & even same day shipping the bar for fast shipping and therefore efficient warehouse communication to support it has been raised drastically. Luckily the constant leaps in mobile technology can be applied in innovative ways to warehouse communication which ultimately leads to increased productivity, lower overhead, and increased revenue.

Winning the Race with Efficient Productivity

There are several areas that mobile applications can lend their agile capabilities to increasing productivity in warehouse management and communication. The most pertinent of these according to Manufacturing.net is order picking and packing, saying that:

“Timing and accuracy play a key role in successfully filling an order. These factors have a direct effect on picking labor costs and orders picked per hour metrics. Reducing time to complete a process/order is directly tied to customer satisfaction.”

 

The challenge here is two-fold: How do you efficiently communicate what needs to be in an order & how do you make sure pickers know where that inventory is? Mobile inventory management applications solve both of these issues by creating a central place to easily view, generate & fulfill orders all in one place. With mobile solutions, Managers and workers can carry mobile devices with them to help locate where a specific item is located and directly scan it to add or remove it from their list depending on the task. Mobile inventory tracking such as this also allows for more accurate counts, by making sure items are not subtracted until they are physically scanned & handled.

We’ve taken this idea a step further with a research and development project for a client that featured smart glasses that did this step for them instead of having to manually input codes or carry a cumbersome scanner. Applying mobile technology to warehouse communications also means managers no longer have to be tied to a desk or constantly walking back and forth with features like instant reporting and chat interfaces that allow them to make inventory decisions directly from the work floor and communicate with employees instantaneously.

Integrating Mobile Lowers Overhead Costs & Increases Revenue

Many companies think that upgrading tech will be costly and time-consuming but mobile doesn’t have to be either of those and can significantly save money in the long run. One of the strongest selling points for mobile inventory and warehouse apps is that they can then be run on out-of-the-box devices. By eliminating the need for specialty and single-use devices, you no longer have to worry about expensive and untimely maintenance issues that not only impede workflow but pull money from your budget. Both mobile devices and applications are more easily updated with software updates or patches, giving a longer life to both instead of being stuck with legacy equipment that becomes more and more inefficient. Furthermore integrating a custom warehouse app gives you a more intuitive & relevant user interface, meaning you need to spend less time on training your workforce.

Increased revenue is the end goal for just about all companies, it is the easiest metric for measuring success and how you grow your enterprise. All of the things we’ve highlighted above should ultimately be drivers to increasing your bottom line. Warehouses are increasingly being viewed not just as pure cost centers in which operational focus is placed almost exclusively on wringing out inefficiencies and inaccuracies in order picking, but as a powerful asset that can drive profitable growth for the business with a heightened focus on improving inbound, storage, and outbound materials handling.

Finding the mobile warehouse solution for your business will significantly help your warehouse operations adapt to the quickly changing landscape of retail, e-commerce, and business in general as we continue to live in an increasingly interconnected and on-demand world.

Laura Little

Laura Little

Laura graduated from Virginia Commonwealth University and holds a degree in Creative Advertising. She’s passionate about authentic narratives, finding what a brand believes in and figuring out how to best translate that to consumers. She feels fortunate to have worked in and have experience in just about every part of the advertising and marketing world. In her downtime she can be found going on outside adventures with her dogs, enjoying local breweries, or doing experiments in her kitchen.

How to Align the Right Supply Chain App With Your Needs

How to Align the Right Supply Chain App With Your Needs

Prioritizing the strategic importance of technology that supports your supply chain is essential. Mobile Solutions are an integral and expanding component of the technology landscape that support all steps of the manufacturing process. Organizations that have mobilized solutions such as enterprise resource planning (ERP), enterprise asset management (EAM) or field service management (FSM), warehouse management system (WMS), Inventory Management System (IMS), etc. are more prepared tackle the overall digital transformation. By 2018, mobile has served as the key to unlocking the “supply chain digital transformation.” Shockoe is creating supply chain apps that are designed to support new production strategies, reduce time to manage and log resource and production activity, bring greater accuracy to tracking and analyzing data, and support the automation of production centers. Within the supply chain, more specifically warehouse management, manufacturing, and distribution, there are several opportunities for leveraging mobile in the workforce. In working with our clients, we believe the list below is the starting point for mobile solutions. These supply chain app options are aimed at supporting the overall manufacturing and sales process through an ecosystem of inventory apps, customer-facing apps, and order fulfillment apps. supply chain management diagram

Mobile Apps to Consider by Point in the Supply Chain

 

Planning & Production Apps

  • Managing parts and resource staffing requirements
  • Production workflow management
  • Inventory and asset management apps
  • Logistics and supply chain coordination

arrow supply chain app

Production Planning Apps are not only used for the manufacturing business, but also across the entire supply chain.  Production planning apps allow users to manage the overall process in three primary ways:

  1. Augment your Production Planning System:  A Production planning solution is only the first step in transforming the planning process from a manual process based on spreadsheets to one based on integrated sales history-based forecasting and confirmed sales orders/release schedules, Mobile Apps allow your team to make real-time decisions based on information obtained through a Mobile App
  2. Managed Strained Resources:  What happens when you find out that there will be a 10 percent increase in demand next month but you have no idea if you’ve got the labor, warehouse space, or raw materials to fill these extra orders? What happens if your demand shifts mid-cycle? Master scheduling can be controlled via a computer, but why not get the insights to do it on your mobile app.
  1. Disconnected Plant Floor:  Errors mean waste, unhappy customers, and non-compliance. As your business grows, mobile becomes the only way to ensure your team can “be there”, on the plant floor, to know what’s going on at any given moment. Operators and Managers need to focus on the task at hand, not on managing issues.  Mobile Apps give Managers and Operators a complete view of the manufacturing operation that is available to the entire organization but on the shop floor.

 

Order Management & Accounting Apps

Mobile Apps that fall into this category include:

  • Order management apps, pricing, and fulfillment
  • Real-time quote generation

ac moore order management app

There are two types of helpful applications that support the order management and accounting process.  Order Management and Fulfillment Apps will support users with:

  • Two-way transmission of orders, inventory, shipping, & Invoices
  • Track process of fulfillment orders real-time and close the fulfillment loop
  • Enable shippers to track order progress

Real-time quote generation also has some great benefits.

 

Order Fulfillment and Compliance Apps

Mobile Apps that fall into this category include:

  • Order status and delivery
  • Product traceability and quality
  • Logistics and supply chain apps
  • Machine level compliance and intelligence

Much like the production & planning stage, order fulfillment falls apart due to inefficient operations, a lack of integration, and a lack of inventory knowledge.  Mobile Apps can relieve the following key pain points experienced:

  1. Automate Entry for Order Fulfillment: We see our clients continue to use a manual process for order entry as their go-to option. This leads to all kinds of inaccuracies in the order, from the items needed to fulfill it, to where it is being sent. Mobile apps can equip operators with the equipment necessary to eliminate manual entry errors on things like product SKUs, quantity, and shipping addresses
  2. Resolve Inventory Inaccuracies & Inefficiencies‍: Having the right inventory is only half the challenge, operators need to know where it is and how to get to it.  Mobile is not the only solution for this, there are two things your team needs to do, (1) Make sure to have a floor layout that makes inventory visible first, from a Mobile Perspective, track inventory as it enters and leaves the warehouse in a centralized system, mobile apps enable employees to do this quickly and efficiently, real-time.
  3. Team Communication in Fulfillment: Miscommunication can cause shipping delays just as much as a disorganized warehouse. If the entire team isn’t on the same page and has no direct access to managers, too much time is spent “chasing” down the right person.  As a warehouse manager, you may have all the vendor information you need – but that won’t help your team unless you communicate the information that they need, with a supply chain app in hands, your team can be in constant communication.

When prioritizing and considering mobile supply chain solutions to support the manufacturing, administrative, and customer engagement process, Shockoe suggests using three key metrics to prioritize supply chain app development versus alternative mobile applications or the overall technology portfolio.

Reduce Manual Labor and Increase Productivity

Delivering a successful customer experience and subsequently driving more revenue requires mobile leadership and prioritization activities to go beyond the building of apps for customer-facing roles (i.e. Work Order Management or Sales). Back-office workers, technicians, and managers need mobile access to enterprise systems to better perform their jobs and service customers more efficiently. Think about creating a better mobile experience by integrating with ERP or WMS systems for, example, giving employees more freedom to do their jobs wherever and whenever. Critical features for a mobile supply chain functions are scheduling, measuring, and dispatching the workforce. Leveraging the mobile context for such, will allow organizations to accurately measure and improve the productivity of each individual. Which finally in turn will allow organizations to accurately measure and improve the productivity of each individual. Going beyond technology and mobility, an app that enables the workforce can also collect data essential to future planning. The organization can better analyze how much more work it can take on and whether there are enough people to support the forecasted workload.

Revenue

Historically speaking, revenue was only accounted for from a customer standpoint. Integrating new technologies to create a mobile supply chain allows monitoring and assessing revenue percentage at the process level. Being able to quickly obtain the percentage of revenue contribution at each step in the order to cash process is valuable information for real-time decision making and forecasting. Mobile solutions can track performance at each step which gives both locations with context. These values help indicate whether a measured process is underperforming or contributing significantly to the overall revenue. Whether it’s an investment in an asset management app or an inventory management app, whatever it is, a more granular overview of information through your processes can help target your areas of growth, need, and reward in the supply chain.

Automation

Automation, IOT, and Machine Learning are popular terms. What manufacturing companies really need to worry about and set goals for is the extent of the automation planned and how these emerging technologies can help. Once the business understands its goal, mobile solutions can embrace IoT devices to enable the desired automation. Allowing employees to monitor automated processes on mobile devices can decrease error rates, speed fulfillment processes, and ultimately settle cash faster. The continuing development and availability of newer and better technology imply that processes can be made to work faster, repetitive work can be automated, FTEs can be reduced, operations can be systemized, and customer handling can be improved. Tackling automation, IOT, or machine learning require a set of tough decisions:

  • To what level will I automate the process?
  • What aspects of the process will I automate?
  • What tools will my employees use to monitor and be alerted of the process?

The mobile supply chain is the next era of communication, collaboration, and responsiveness to the customer, driven by the ability to meet ever-more-stringent deadlines and delivery dates. Start from your key challenges and business objectives, use the right metrics to prioritize, and plan your journey for successful implementation of mobile, IoT and emerging technologies. Shockoe works in the supply chain to prioritize manufacturing apps and technology that support essential business processes. Mobile Solutions are at the forefront of all digital transformations and can support all areas of the manufacturing process. The right mobile app can help manage several areas of the manufacturing process and beyond.

Alejandro Otañez

Alejandro Otañez

COO

Alex has more than 10 years of international experience in Strategic IT Transformation and Custom App Development. His expertise in various industries ranges from Consumer Goods to Retail to Finance while assisting clients in the areas of Business Strategy & Development, Security & Compliance, and Technology Transformation. As one of Shockoe’s founding members, Alex is focused on business strategy, security & compliance, digital innovation, mobile management, and operational transformations.

The Key to an Effective Warehouse Management App: User-Centric Design

The Key to an Effective Warehouse Management App: User-Centric Design

Custom mobile apps are effective tools for improving the Warehouse and Inventory Management process. However, a robust application with the best-of features can be rendered ineffective if the product itself is not usable. The key to launching an effective warehouse app is to create a user-centric experience that genuinely aids employees in improving productivity and performance. Let’s dive into what this means in more detail.

What do we mean by user-centric?

We mean: understanding the user, tasks, and environment surrounding the application. Poor usability can lead to user frustration and in turn, reduce overall productivity. The need for user-centric design is crucial when speaking of warehouse and inventory management applications — after all, these are the tools equipped to workers to perform their job effectively on a daily basis.

Some off-the-cuff strategies I’ve seen personally help Shockoe in deploying a great warehouse application include:

  • Get to know your user
  • Understanding the work environment and its nuances
  • Be flexible and aim for continuous improvement.

This was the core of our strategy with one of the nation’s largest electronics distributors, a warehouse management app that would go on to recognize our team with RichTech’s 2018 Technology Builder Award.

Know your user

Employees know the in’s and out’s of their daily tasks better than anyone else. They can often expose golden nuggets of information to help improve app flows as well as desired user-experiences. Whether you shadow workers or conduct interviews, the key is to expose needs and pain-points in the day-to-day. This information will give you the greatest insight as to how to design an effective warehouse solution that is easy and truly helps improve daily task performance. Below are pain-points we uncovered with one of our clients as well as how we applied that knowledge to the next generation of their warehouse application:

  • Pain-Point A: Product walkthroughs with workers revealed a need for clear, simple, and intuitive user flows. Solution: Minimal screen design, and clear CTAs on-screen to reduce distractions.
  • Pain-Point B: The small interface and visual elements on the previous scanners made it difficult to interact with the device. Solution B: Go large! We helped implement larger screens and from a design-standpoint included large text and bolder visual elements
  • Pain-Point C: Workers struggled to juggle boxes and packages while attempting to interact with small format scanners. Solution C: Large format CTA’s now make it easier to interact with the screen, even with busy hands

 

Understand the warehouse environment

Where a warehouse app is used can greatly influence whether a design is effective or not. Some important environmental factors to note include: lighting, noise, common distractions, and present equipment. Warehouse environments produce unique challenges; fork-lifts, conveyor belts, endless rows of supply, and obstacles should all be documented and considered in the user-experience — here are a few things we saw at within the warehouse that impacted the next version of the application:

  • Pain-point A: Multiple environmental distractions (steady stream of noise, bustling workers) and placing the tablet down made it likely to miss important alerts. Solution A: We made notifications large, bold, and sticky (make sure they stay on screen until dismissed)
  • Pain-point B: Finding the right box with the right item could be tricky at times Solution B: We Incorporated a label identification system (Area, Aisle, Bay, Tier, Position) into the app so that workers can match it to the physical product. see example below:

 

Flexibility & continuous improvement

This is a part of a strategy that’s unfortunately often overlooked. Companies are constantly growing, changing, and improving; the tools in place should do the same. Even great apps should be tested with users and iteratively improved over time. They should also be designed with flexibility in mind — sometimes the best ideas don’t work as expected, and being able to pivot to an alternative solution is critical to the app’s overall success. Not doing so, could mean greater failure for the rest of the features that do in-fact work. Here are a few areas we saw the need to pivot with our client’s  warehouse solution and ensure its ongoing success:

  • Pain-point A: App testing revealed manual-workarounds being performed by employees to relabel already picked inventory Solution: Workers were given a custom print option to create labels that would reflect proper quantities and date codes all from within the app
  • Pain-point B: The company’s employees required different features for different roles. Solution: We incorporated a responsive user experience that would shift the interface to match the job function of the current user.

 

Every company, every process, and every employee are different —  that’s why taking a user-centric approach to design is essential towards an app’s success. It is up to the designers and strategist to always have the end-user in mind. A blanketed approach to a user-experience could yield great results for one warehouse management system, but a disaster for another. The key to a successful inventory management app is to start from the ground floor and focus on understanding the end-user, the environment, and remain flexible with your team.

Angela Balzano

Angela Balzano

UX/UI Designer

Ange is a Rhode Island native with a passion for problem-solving and a flair for design. She specializes in strategy work and creating intuitive user experiences. Her long-standing career in technology design has led her to become mobile UX/UI expert with an emphasis on enterprise application usability… oh, and she loves crime podcasts.

Asset/Inventory Management Apps in Record Time with Flutter

Asset/Inventory Management Apps in Record Time with Flutter

Shockoe specializes in utilizing tools which can most efficiently provide a beautiful experience for a given project. We have a history with cross-platform frameworks, as they can often quicken the development period for a mobile app considerably. Many of our projects were historically built on Titanium, and a few more recently were undertaken with React-Native.

When Flutter was announced, we knew we had to keep a close eye on it, and we were eager for it to reach the point where it was mature enough to build a robust production app. The results were astounding. Not only does it ease many of development pains present in other cross-platform frameworks, it also gives you beautiful UI out of the box, and extraordinary speed as it is blazingly fast to develop. In fact, the entirety of the development you will see later in this post was completed single-handedly in a matter of hours!

Flutter for Inventory Management Apps

At Shockoe, we have a point of creating great inventory/asset apps the help manage resources, assets, and inventory at a number of large-scale companies. A few reasons why Flutter has been a particularly great fit for developing these kinds of apps include:

  • List Convenience: Turning a raw data list of assets and inventory into an actual list laid out on screen couldn’t be easier. It can be accomplished in a handful of lines of code.
  • Beautiful by Default: Apps in this category have a heavy focus on functionality. A framework which looks good in its most basic state lets you focus on the utility and devote as much time as you decide to enhance UI and delivering the right content.
  • List Performance: Flutter renders every pixel on the screen itself, allowing for a performance unparalleled by other frameworks. It touts its ability to maintain 60fps, and scrolling is buttery-smooth even on massive lists.
  • Empty/Loading State Simplicity: Most screens will be heavily data-driven. In some environments, displaying states like waiting for an API response or failing to connect can become extremely cumbersome. Flutter makes it easy to build a UI which reacts to these in-between moments gracefully.
  • Object-Oriented: Unlike Titanium and React-Native, which use Javascript, Flutter apps are written in Dart. This offers a number of benefits, like the lessened runtime error rate of strongly typed languages. The reason why it is perfect for this case is that Object-Oriented design allows for easy 1:1 mappings between real objects and their representations in code. Are you a retailer which specializes in shoes? Well, chances are your app is going to have a class Shoe and an instance of it is going to tell you everything you need to know about that specific shoe.

In this post, we’re going to take a look at building an inventory management app and not just the Flutter bit. This post includes a fully functioning Node.js backend as well — ensuring you successfully deliver your message and content to your users.

Note: This will not be a step-by-step guide, as that would be difficult to digest at a high level. Instead, we will look at each piece and break down the important components.

Inventory App Features We’ll be Building

Below is everything entailed in going from an unstarted project to a functioning application pulling real data. Here is what we will end up with:

Let’s dive in!

Basis

We will use the example of a library — yes, the variety filled with a book! A library is essentially a warehouse filled with inventory (in this case, books). For many businesses an inventory application, at its core, would support browsing and tracking items. In the context of a library, those functionalities manifest themselves in the following ways:

  • Browsing
    • view the full catalog
    • search for a specific title
    • view information about a specific title
  • Tracking
    • see a title’s availability
    • check out a copy
    • return a copy

Our app will handle all of the above.

Setup

Database

MongoDB will be used to store the data. There is no special setup required, we just load all the items into a collection and later run the Node.js server on the same machine to leverage Mongo’s already exposed localhost connection. Most likely, when building an app of this type, it will be used to access an existing dataset. The data in this example will be a subset of the most popular titles on Project Gutenberg supplemented with Wikipedia details.

Backend (API)

For our server, we will be using hapi with a few smaller dependencies like the official Node.js MongoDB drive and boom for error handling. Once hapi is installed, we must create our startup file. This will get the server up and running to fulfill requests. Let’s use index.js.

'use strict';

const Hapi = require('hapi');
const routes = require('./routes');

const server = Hapi.server({
port: 3000
});

server.route(routes.allRoutes);

const init = async () => {

await server.start();
console.log(`Server running at: ${server.info.uri}`);
};

process.on('unhandledRejection', (err) => {

console.log(err);
process.exit(1);
});

init();

Tiny, right? Hapi requires very little boilerplate. The majority of this is ripped right from hapi’s Getting Started guide. Besides removing the host property on the server configuration object in order to fall-back to the machines hostname, the only custom line is as follows:

server.route(routes.allRoutes);

This line imports and registers all of the endpoints we define in our second, and final, file: routes.js. We separate these so that the server configuration doesn’t get drowned out by the much larger endpoint definitions. In a more complex app, we would likely want multiple files which logically group endpoints into smaller buckets. Here is routes.js with an example endpoint. Its only job is to export an array of configuration objects.

const Boom = require('boom');

exports.allRoutes = [
  {
    method: 'GET',
    path: '/',
    handler: async (request, h) => {
      return 'Hello world';
    }
  }
];

 

Flutter

Enter Flutter! When creating a new Flutter project through IntelliJ, a main.dart file is created for a basic sample app which implements a counter. This is helpful when learning, but we need to rip out some of that starter code. Here is a single page app which we can use as a starting point.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Library',
      theme: ThemeData(
        primarySwatch: Colors.deepOrange,
      ),
      home: CatalogPage(),
    );
  }
}

class CatalogPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Catalog'),
      ),
      body: Center(
        child: Text('List of books'),
      ),
    );
  }
}

 

 

Catalog

Backend

Now that we’re set up, let’s start serving up data. We replace the example endpoint we defined before with one which returns the full list of books in the database.

const MongoClient = require('mongodb').MongoClient;
const Boom = require('boom');

exports.allRoutes = [
  {
    method: 'GET',
    path: '/bookList',
    handler: async (request, h) => {
      let client;
      try {
        client = await MongoClient.connect('mongodb://localhost:27017');
        let books = client.db('inventory').collection('books');

        // fetch all books
        return await books.find({},
          {
            projection : {
              _id: 0,
              id: 1,
              title: 1,
              authors : 1,
            }
          }
        ).toArray();
      } catch (e) {
        console.error(e.message);
        return Boom.internal(e);
      } finally {
        if (client && client.close){
          client.close();
        }
      }
    }
  }
];

You may notice async/await syntax. As it is available in both recent Node.js versions and Dart, we will use it throughout the backend and the app. There isn’t too much going on here. We connect to MongoDB, specifically the collection books in the database inventory, and run a find query with an empty filter object (first argument) so that all records are pulled. For cleanliness of data, we project only the properties of a book which we would be interested in when listing them en masse.

Flutter

The first thing we need to define is the representation of a book. We will go ahead and include all fields we need to be known for a book, even though only a few of them will be populated from the results of the /bookList endpoint.

class Book {
  final String id;
  final String title;
  final List<String> authors;
  final String releaseDate;
  final String description;
  final int totalCopies;
  final int availableCopies;

  /// Creates a Book instance out of JSON received from the API.
  Book.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        title = json['title'],
        releaseDate = json['releaseDate'],
        description = json['description'],
        totalCopies = json['totalCopies'],
        availableCopies = json['availableCopies'],
        authors = json['authors'].retype<String>();
}

We will use the “Serializing JSON inside model classes” strategy shown in Flutter’s JSON and serialization guide. CatalogPage is a Stateless widget, because the full screen including the appbar doesn’t need to be re-rendered in the future, just the content. For that, we create a Stateful Widget, called CatalogList, which we will place in the body of CatalogPage. To keep this example concise, network requests will be made directly from widgets. It is better to practice to split them out into a non-UI library. Here is Catalog with basic display functionality complete followed by a breakdown below.

 

/// The list of books.
class CatalogList extends StatefulWidget {
  @override
  _CatalogListState createState() => _CatalogListState();
}

class _CatalogListState extends State<CatalogList> {
  /// All books in the catalog.
  List<Book> books;

  /// Books currently being displayed in the list.
  List<Book> displayedBooks;

  /// Kicks off API fetch on creation.
  _CatalogListState() {
    _fetchBookList();
  }

  /// Fetches the list of books and updates state.
  void _fetchBookList() async {
    http.Response response = await http.get('http://<API location>/bookList');
    List<Map<String, dynamic>> newBooksRaw =
        json.decode(response.body).retype<Map<String, dynamic>>();
    List<Book> newBooks =
        newBooksRaw.map((bookData) => Book.fromJson(bookData)).toList();
    setState(() {
      books = newBooks;
      displayedBooks = books;
    });
  }

  @override
  Widget build(BuildContext context) {
    return displayedBooks != null
        ? Column(
            children: <Widget>[
              new Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ListView.builder(
                    itemBuilder: (BuildContext context, int index) => Card(
                          elevation: 2.0,
                          child: ListTile(
                            title: Text(
                              displayedBooks[index].title,
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                            ),
                            subtitle:
                                Text(displayedBooks[index].authors.join(' | ')),
                          ),
                        ),
                    itemCount: displayedBooks.length,
                  ),
                ),
              ),
            ],
          )
        : Center(child: CircularProgressIndicator());
  }
}
Fetch Data

When the CatalogList widget is created, we immediately want to fetch the data on all books from the backend. We go ahead and create two list references, one for all the data downloaded and one for data currently displayed, as we know search functionality is coming and we won’t always be displaying the full catalog on screen. When data is first downloaded, though, these will be the same. We take advantage of the fromJSON serialization constructor we created to convert the backend’s JSON response into a list of formed Book objects in one list mapping call.

/// All books in the catalog.
List<Book> books;

/// Books currently being displayed in the list.
List<Book> displayedBooks;

/// Kicks off API fetch on creation.
_CatalogListState() {
  _fetchBookList();
}

/// Fetches the list of books and updates state.
void _fetchBookList() async {
  http.Response response = await http.get('http://<API location>/bookList');
  List<Map<String, dynamic>> newBooksRaw =
      json.decode(response.body).retype<Map<String, dynamic>>();
  List<Book> newBooks =
      newBooksRaw.map((bookData) => Book.fromJson(bookData)).toList();
  setState(() {
    books = newBooks;
    displayedBooks = books;
  });
}

Try/catch around the async body of _fetchBookList is omitted for readability. Make sure to catch possible exceptions/errors in production.

/// All books in the catalog.
List<Book> books;

/// Books currently being displayed in the list.
List<Book> displayedBooks;

/// Kicks off API fetch on creation.
_CatalogListState() {
  _fetchBookList();
}

/// Fetches the list of books and updates state.
void _fetchBookList() async {
  http.Response response = await http.get('http://<API location>/bookList');
  List<Map<String, dynamic>> newBooksRaw =
      json.decode(response.body).retype<Map<String, dynamic>>();
  List<Book> newBooks =
      newBooksRaw.map((bookData) => Book.fromJson(bookData)).toList();
  setState(() {
    books = newBooks;
    displayedBooks = books;
  });
}

Try/catch around the async body of _fetchBookList is omitted for readability. Make sure to catch possible exceptions/errors in production.

Build a List

Here is where we see Flutter start to shine. To convert this list of Book data into a rendered list on the screen, all we have to do is write an itemBuilder function which returns what a given item in the list will look like, then pass in the list of data and it’s length. We use a Material Design Card containing a ListTile- a prebuilt widget which displays a title and subtitle (and optionally additional inner widgets).

child: ListView.builder(
  itemBuilder: (BuildContext context, int index) => Card(
        elevation: 2.0,
        child: ListTile(
          title: Text(
            displayedBooks[index].title,
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          subtitle:
              Text(displayedBooks[index].authors.join(' | ')),
        ),
      ),
  itemCount: displayedBooks.length,
),

That’s all it takes to build a ListView which is ready for production. It will lazily render new rows as they are scrolled into view, gracefully handle changes to the list of Books, adapt scrolling behavior to OS, and perform fantastically. Much of the above is styling as well. We could get something functional in half as many lines.

 

child: ListView.builder(
  itemBuilder: (BuildContext context, int index) => ListTile(
        title: Text(displayedBooks[index].title),
        subtitle:
            Text(displayedBooks[index].authors.join(' | ')),
      ),
  itemCount: displayedBooks.length,
),
Handle Loading State

When the Catalog List is created, the data list which our ListView is populated from, displayedBooks, is null.

List<Book> displayedBooks;

 

Once the data fetch is complete, that variable will point to a valid List.

setState(() {
  books = newBooks;
  displayedBooks = books;
});

 

Once this occurs, the ListView can start rendering rows. In the meantime, we need to render something different. If handling moments like these require a lot of development effort, it can feel counterproductive to tackle them right out of the gate while true functionality is still being worked out. This can lead to polish/UX tasks being put on the afterburner. With Flutter, it’s easy to handle loading during our first pass at the screen. We just use a ternary expression in the build function to describe an alternate visual while displayedBooks is still null.

@override
Widget build(BuildContext context) {
  return displayedBooks != null
      ? Column(
          // ...rest of widget hierarchy for loaded state
        )
      : Center(child: CircularProgressIndicator());
}

 

Search

Supporting search requires two main changes.

  1. Add a search input field which fires an event when it changes
  2. Add a search function to filter the displayed list when the search event is fired

An input field which fires an event is achieved with the combination of TextField and TextEditingController

/// The controller to keep track of search field content and changes.
final TextEditingController searchController = TextEditingController();
child: TextField(
  decoration: InputDecoration(hintText: 'Search for titles...'),
  controller: searchController,
),

With the search bar in place, we can register a listener when the CatalogList is created to fire the function which will filter the full book list down to results to be displayed and update state. If the search text becomes empty, the list is set back to the full catalog.

/// Kicks off API fetch on creation.
_CatalogListState() {
  _fetchBookList();
  searchController.addListener(_search);
}
/// Performs a case insensitive search.
void _search() {
  if (searchController.text == '') {
    setState(() {
      displayedBooks = books;
    });
  } else {
    List<Book> filteredBooks = books
        .where((book) => book.title
            .toLowerCase()
            .contains(searchController.text.toLowerCase()))
        .toList();
    setState(() {
      displayedBooks = filteredBooks;
    });
  }
}
Navigation

The Catalog page is complete, and now we need to be able to take a deeper look at an individual item. The next page will be called DetailPage, so we’ll rig up each item in the list to move forward to the respective book’s details. Conveniently, ListTile has builtin touch handling, so we can just add a single property

onTap: () {
  Navigator.of(context).push(MaterialPageRoute(
      builder: (BuildContext context) {
    return DetailPage(displayedBooks[index].id);
  }));
}),

We only pass the id instead of the Book instance, since we will be fetching the book’s most up-to-date full data from the backend when loading DetailPage anyways.

Details

Backend

We add a new endpoint configuration object onto allRoutes to return full details of a title. It is nearly identical to the full listing except we switch to findOne, add a filter for an id passed from the app, and project additional fields. We also call out to a function to calculate the number of copies available, but we will wait to see that in the Tracking section.

{
  method: 'GET',
  path: '/book',
  handler: async (request, h) => {
    let client;
    try {
      client = await MongoClient.connect('mongodb://localhost:27017');
      let books = client.db('inventory').collection('books');

      // fetch matching book
      let book = await books.findOne(
        {
          id: request.query.id
        },
        {
          projection : {
            _id: 0,
            id: 1,
            title: 1,
            authors : 1,
            releaseDate : 1,
            description: 1,
            totalCopies: 1,
            checkedOutTo: 1
          }
        }
      );
      setAvailableCopies(book);
      return book;
    } catch (e) {
      console.error(e.message);
      return Boom.internal(e);
    } finally {
      if (client && client.close){
        client.close();
      }
    }
  }
},
Flutter

The basic DetailPage widget is a bit larger, but most of the functionality will look familiar from CatalogList. This time we make DetailPage itself the Stateful Widget, since we want to allow the AppBar to update with the book’s title once data is loaded.

/// The screen which displays the full details of a given book.
class DetailPage extends StatefulWidget {
  final String bookId;

  DetailPage(this.bookId);

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

class _DetailPageState extends State<DetailPage> {
  /// The full book data.
  Book book;

  /// Kicks off API fetch on creation.
  _DetailPageState() {
    _fetchBookDetails();
  }

  /// Fetches the books details and updates state.
  void _fetchBookDetails() async {
    http.Response response =
        await http.get('http://<API location>/book?id=${widget.bookId}');
    Map<String, dynamic> newBookRaw = json.decode(response.body);
    Book newBook = Book.fromJson(newBookRaw);
    setState(() {
      book = newBook;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(book?.title ?? ''),
      ),
      body: book != null
          ? new Center(
              child: new SingleChildScrollView(
                child: Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Card(
                    elevation: 5.0,
                    child: Center(
                        child: Padding(
                      padding: const EdgeInsets.fromLTRB(24.0, 0.0, 24.0, 24.0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        crossAxisAlignment: CrossAxisAlignment.center,
                        children: <Widget>[
                          _BodySection('Author', book.authors.join('\n')),
                          _BodySection(
                              'Release Data', book.releaseDate ?? 'N/A'),
                          _BodySection('Description', book.description),
                        ],
                      ),
                    )),
                  ),
                ),
              ),
            )
          : Center(child: CircularProgressIndicator()),
    );
  }
}

class _BodySection extends StatelessWidget {
  final String title;
  final String content;

  _BodySection(this.title, this.content);

  @override
  Widget build(BuildContext context) {
    return new Padding(
      padding: const EdgeInsets.only(top: 24.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Text(title, style: Theme.of(context).textTheme.title),
          Text(content, style: TextStyle(color: Colors.grey[700]))
        ],
      ),
    );
  }
}

We could have fetched all these details back on the CatalogPage and simply passed them through the be displayed on the DetailPage. The reason we opt for pulling the data fresh is to ensure we have the very latest details on an item when we view it. In this example, it’s unlikely that any of the fields we are displaying would change frequently. In other industries, however, a DetailPage might be displaying volatile data. These considerations especially come into play when we add our next section.

Tracking

We will support two operations on books: checking out and returning. These operations will drive the number of copies available to be checked out by other users as well. When a user checks a book out, they are grabbing it from the library and marking one of the copies as taken by them. When they return a copy, they are again providing their name and stating that they have placed the copy back into the library. In this example, no unique identifiers are used for individual copies, however, it would be a small change to add an additional field to the app and modify the backend to accept a copy identifier (potentially a barcode). The three fields in the database driving these interactions will be:

  • totalCopies – total number of copies of a title which the library has in circulation
  • checkedOutTo – array of names to which copies are checked out along with one virtual field calculated at request time
  • availableCopies – number of copies in the library available for checkout (total copies – checked out copies)

 

Backend

We add two endpoint configuration objects. /checkOutBook will push a name onto the checkedOutTo array, and /returnBook will splice a name out of it.

{
  method: 'POST',
  path: '/checkOutBook',
  handler: async (request, h) => {
    let client;
    try {
      client = await MongoClient.connect('mongodb://localhost:27017');
      let books = client.db('inventory').collection('books');

      // update book data with name added to check out list
      let bookResult = await books.findOneAndUpdate(
        {
          id: request.payload.id
        },
        {
          $push : { checkedOutTo : request.payload.name }
        },
        {
          projection : {
            _id: 0,
            id: 1,
            title: 1,
            authors : 1,
            releaseDate : 1,
            description: 1,
            totalCopies: 1,
            checkedOutTo: 1
          },
          returnOriginal : false
        }
      );
      let book = bookResult.value;
      setAvailableCopies(book);
      return book;
    } catch (e) {
      console.error(e.message);
      return Boom.internal(e);
    } finally {
      if (client && client.close){
        client.close();
      }
    }
  }
},
{
  method: 'POST',
  path: '/returnBook',
  handler: async (request, h) => {
    let client;
    try {
      client = await MongoClient.connect('mongodb://localhost:27017');
      let books = client.db('inventory').collection('books');

      // get current list of checked out copies
      let currentBookData = await books.findOne(
        {
          id: request.payload.id
        }
      );
      let nameIndex = currentBookData.checkedOutTo.indexOf(request.payload.name);
      if (nameIndex !== -1){
        currentBookData.checkedOutTo.splice(nameIndex, 1);
      }

      // update book data with name removed from check out list
      let bookResult = await books.findOneAndUpdate(
        {
          id: request.payload.id
        },
        {
          $set : { checkedOutTo : currentBookData.checkedOutTo }
        },
        {
          projection : {
            _id: 0,
            id: 1,
            title: 1,
            authors : 1,
            releaseDate : 1,
            description: 1,
            totalCopies: 1,
            checkedOutTo: 1
          },
          returnOriginal : false
        }
      );
      let book = bookResult.value;
      setAvailableCopies(book);
      return book;
    } catch (e) {
      console.error(e.message);
      return Boom.internal(e);
    } finally {
      if (client && client.close){
        client.close();
      }
    }
  }
}

We also add a quick helper method to routes.js to calculate available copies.

function setAvailableCopies(book){
  book.availableCopies = book.totalCopies - (book.checkedOutTo ? book.checkedOutTo.length : 0);
}
Flutter

Beneath the other body sections, we add availability details and a small form for checkout/return. Since both actions require the same info, they can share an input box.

_BodySection('Available Copies',
    '${book.availableCopies} / ${book.totalCopies}'),
Column(
  children: <Widget>[
    TextField(
      decoration:
          InputDecoration(hintText: 'Enter name'),
      controller: nameController,
    ),
    new Padding(
      padding: const EdgeInsets.only(top: 16.0),
      child: new Row(
        mainAxisAlignment:
            MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          RaisedButton(
            child: Text('Check Out!'),
            onPressed: fieldHasContent &&
                    book.availableCopies > 0
                ? _checkOut
                : null,
          ),
          RaisedButton(
            child: Text('Return'),
            onPressed:
                fieldHasContent ? _return : null,
          ),
        ],
      ),
    )
  ],
)

These buttons call functions to pass the current book id and entered a name to the backend, and will update state with the new version of book details returned. This ensures that the available copy count will stay in sync with actions performed.

Each button is conditionally disabled by passing null to its onPressed property. Both buttons are disabled by the fieldHasContent flag which is set when the name input field is empty, and the check out button is additionally disabled when there are no available copies.

We have a functional, performant, aesthetically pleasing app with associated backend all in ~500 lines of code. Creating a solution for Asset/Inventory Management has never been easier, and Flutter continues to improve daily. Don’t let it’s “beta” tag fool you, Flutter is production ready. In fact, we just built a production Inventory Management application for Belden Brick, so that the brick distributors they work with can access browse their inventory, search products, view product images, order samples, and more!

Below you will find the full files from this demo.

Flutter: https://bitbucket.org/snippets/shockoe/7eMxEq
Hapi: https://bitbucket.org/snippets/shockoe/ne8jR6

 

 

Related post:

Three Reasons Flutter is a Viable Cross-Platform Framework
Google Flutter goes Beta at #MWC18