A Summer of Interning: What we Learned @ Shockoe

A Summer of Interning: What we Learned @ Shockoe

Our Shockoe family is comprised of people with different skills, talents, and backgrounds — we know it’s what allows us to grow and learn as a team. It’s for this reason that our internship program took on a different shape this year. Thanks to the prep work and persuasive initiatives of our very own Mason Brown, Shockoe put together a comprehensive program focused on leveraging our culture of mentorship and growth, not just for the interns, but for our team as well.

Jackie, Matt, and Cameron joined the Shockoe team in May and shared their invaluable contributions to design, strategy, and development. We were fortunate enough to call them part of the Shockoe team, albeit for the short period before they headed back to their studies. We asked that all three share their thoughts and experiences of being an intern — one thing is for certain: we will miss having them around!

Jackie

Finishing my first year as an Experience Design student, I was eager to find a summer internship where I could expand my UX/UI knowledge and gain real client experience; I was super stoked to join Shockoe’s Intern Program. As a Design Intern, my time was split between working with the design team on client projects and the remaining time with my fellow interns on what called “The Intern Project.”

There were many cool firsts for me while working on the Intern Project. It was my first time working with a developer to move a design past the prototype stage. I had to learn the best way to communicate and quickly problem-solve to align with a developer’s process. This was also my first experience with QA testing. I helped evaluate the app for both functionality bugs and inconsistency with the UI. Additionally, this project was the first time I designed using the agile methodology. It was awesome to see how in such a short amount of time we were able to implement a whole process including discovery, design, and development.

Being part of the design team was as fulfilling as it was educational. Everyone was willing to answer any question and quickly welcomed me as a member of the team. I loved the all-hands weekly design meetings where we shared projects and received feedback and advice discussing new tools or resources to better execute our job. I also had an amazing mentor, Sam Carbonell, who helped me define goals for the summer and met with me regularly to help steer my time here at Shockoe. I am so grateful for this mentorship and am happy to say I met all my goals!

Working at Shockoe for the summer has made me more confident in my design skills and I know I walk away with a new breath of knowledge. Thank you, Mason, Sam, Paulina, and Edwin.

Cameron

I came into my internship the same way many others do. I had no idea what to really expect out of the experience, and I was afraid of underperforming in front of my new found co-workers. Shockoe had a prearranged system set up in order to ease me into their environment, masterfully uncovering their processes to me as time went on so that I would not be overloaded with information. Shockoe allowed me to get a first-hand experience into their continuous integration system with technologies like Jira, Bitbucket, and their own accredited App Tracker, as well as work with some of the teams on real applications testing for faults. Before I got here my knowledge of app development came from Native Android, and instead of having me continue with this, they challenged me to branch out and learn the React Native framework.

By learning React Native, I also got experience working with redux, as well as using Firebase for cloud data storage. I utilized these newly established tools in my favorite part of the internship, the Summer Intern Project. This project granted me real experience working with Jackie and Matt, the designer and strategist interns respectively, to develop a high-functioning app by the end of the summer. I got to learn a lot about their processes and the obstacles of their role to both communicate and integrate their ideas into what I then had to develop. The app vision was handled by our amazing internship coordinator, Mason Brown, and our product managers Rebecca and Misty made sure the project timeline and direction stayed on track.

Getting up and bugging senior developers can be a daunting task at some firms, but here at Shockoe, the developers are very understanding and open to help. Whether you are asking to learn more information about a subject, or just have debugging advice they really want to see you grow. I believe that this stems from the amazing culture established at Shockoe.

Shockoe’s emphasis on culture meant they provided many activities outside of work to get co-workers involved. Lunch and Learns were always a great experience since they gave us the chance to sit down with peers for a free lunch and to listen in on a topic from another coworker. Whether that be hearing about the Mystery of the Stolen Bicycle or a lesson on how to self-defense, an amazing experience was always guaranteed to ensue. I was also a member of the Shockoe indoor soccer team (or Shockoe Futbol Club, SFC) which gave me something to do outside of the office and a better chance to get to know my colleagues while playing a game of soccer. Last but not least, by the summer’s end the office held a summer party solely for the team and family member to enjoy a BBQ, corn hole, Jenga, a live DJ, and so much more!

I cannot thank Shockoe enough for the invaluable experience they were able to provide for me. I can leave Shockoe feeling much more confident in my own skills in an industry where you always feel out of place. Thank you, Mason, Paulina, Edwin, Andrew, and the rest of the Shockoe team for hosting this amazing internship program!

Matt

As an Experience Design student, most of my class projects wrap up after concept presentation and prototype walkthroughs. So I was excited for the opportunity to learn how designs and wireframes turn into functioning products, and I don’t think there’s a better company in Richmond to learn that process than Shockoe.

I learned from a variety of opportunities throughout my 9 weeks here. For the Feed Racer intern project, my role was to be the project owner. I worked with Jackie and Cameron to design, develop, and bring our ideas to life. I learned to write user stories, which break down and translate user behaviors into individual features and functionality. One of our biggest challenges was time, specifically not enough of it! I wish we could’ve had 2 more weeks. So we focused on the MVP, and laid out the plans to iterate, and improve. Prioritizing features and fixes was the greatest learning for me because ultimately those are the moments and decisions that make or break success!

One of my other favorite projects and built skillset this summer was focusing on Usability Testing. It was my responsibility to field surveys, screen respondents, and schedule a day of participant interviews. I helped coordinate and participated in the interviews and synthesizing of notes and findings. It was a cool experience to see how people’s thought processes lead them to behave and interact with screens so differently.

Overall, Shockoe was a great place to intern, and the direction and support we received from the Shockoe team was amazing. A big thank you to Mason, Rebecca, Misty, Paulina, and Edwin!

Onwards and Upwards

On behalf of the entire team, thank you Cameron, Matt and Jackie for your hard work, your warm and engaging spirits, and for embracing the culture we proudly call Shockoe — we wish you all the best with your ongoing studies and thank you for helping our team grow and learn from your truly valuable contributions.

Keep an eye out for our next blog post where we will showcase their summer project app aimed at helping local food banks with collection drives and encouraging giving through a fun competitive interface.

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:

inventory app example pulling real data

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'),
      ),
    );
  }
}

 

flutterSetup

 

 

 

 

 

 

 

 

 

 

 

 

 

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());
  }
}

 

basicCatalog

 

 

 

 

 

 

 

 

 

 

 

 

 

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.

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,
),

 

styledListItems

 

 

 

 

 

 

 

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,
),

 

unstyledListItems

 

 

 

 

 

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,
          ),
        ],
      ),
    )
  ],
)

 

 

detailsDisableddetailsEnabled

 

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

Three Reasons Flutter is a Viable Cross-Platform Framework

Three Reasons Flutter is a Viable Cross-Platform Framework

Flutter, as you may know from one of our previous blogs, is Google’s cross-platform mobile development framework. Flutter has recently entered beta as of February 27th. I have only been working with this framework for about a month here at Shockoe, but in my limited exposure, I have seen the many ways in which Flutter can improve our delivery to clients.

 

For most products in any field, there are two important aspects of development: time to market and product quality. If there are ways for us as a company to improve these areas we have an obligation to look into them. With that in mind, I was eager to explore Flutter. Here are three reasons I believe Flutter is a viable cross-platform framework.

 

1. Flutter’s Implementation of Widgets.

 

One of the key fundamentals of Flutter is that everything is a widget. I really like this concept as Google provides many out-of-the-box options to help speed up development. Flutter doesn’t have a bridge to the native world as many other cross-platform frameworks have. Instead, Flutter controls every pixel on the screen, which allows for various great options for custom UI to be tailored to a client’s every need. With that, the widget catalog also gives developers a plethora of options to mimic native controls. This enables any idea to use the catalog to create a beautiful product without compromising quality. The following graphic gives a great visual of how widgets can be laid out for specific components:

 

Flutter’s-Implementation-of-Widgets

 

The section above is made up of four types of widgets. The parent row would be the outermost element containing 3 columns which each contain an icon and text. There’s a huge advantage to having a widget for seemingly almost everything, and the list will only continue to grow.

 

2. Fantastic Debugging Tools.

 

Flutter includes plenty of tools that have sped up development, including one of my personal favorites: the hot reload. With almost any update to the UI, you can use hot reload to instantly see all changes made. Additionally, Flutter offers plug-ins for multiple IDE’s (VS Code, IntelliJ). I personally use IntelliJ and the plug-in provides autofill, debugging, among many others features. Debugging within the IDE contains an abundance of options, but overall, my favorite is the debug painting tool. This tool allows the developer to see the borders on all of their widgets along with the paddings and margins to help clarify where a widget might need to be adjusted.

 

Fantastic-Debugging-ToolsAnother notable feature is the toggle platform tool which allows users to see the UI differences between iOS and Android with the click of a button on the IDE. This debugging tool helps keep development quick and efficient while maintaining quality across platforms.

 

3. Flutter’s Great Documentation.

 

When I was introduced to Flutter, I was excited and daunted by the task of learning a new language. As I started to dive into the documentation and was pleasantly surprised by how thorough it was. It made it extremely easy to start creating applications. Flutter uses Dart, which is a programming language developed by Google. A Javascript or Java developer should be able to transition to Dart with relative ease.

 

Overall, I’m excited to see the future of Flutter. As the community grows, I’m looking forward to seeing the plug-ins that are created and the new projects we will undertake at Shockoe! I have confidence that if a client comes to us with an idea, we will exceed their every need using Flutter to increase development speed without compromising quality.

 

 

 

Editor’s Note: 

Check out the latest from our developers!

SQL vs NoSQL, the ultimate database match

Comparing React Native to Axway Titanium

Kotlin: Three Reasons To Start Using It Today

Google Flutter goes Beta at #MWC18

Google Flutter goes Beta at #MWC18

What is Flutter? 

 

According to Google, Flutter is a mobile UI framework for creating high-quality native interfaces for iOS and Android. As a Google Partner and a company that has focused on building cross-platform mobile solutions for individuals and organizations, it is amazing to see a product like Flutter be released into Beta.

 

Better than other Cross-Platform Solutions

 

First of all, this initiative is backed by Google, which gives it a strong start. Also, the performance and platform integration are seamless and the structure allows us to build at high speed with great performance on both major platforms (iOS and Android.) Sure, there are some bugs and shortcomings, but that is always expected in a Beta version. We are on a trial run and, so far, our team loves it.

 

 

The team at Flutter highlights the benefits best on their Medium Post (Seth Ladd, Product Manager @ Google for Flutter):

 

  • High-velocity development with features like stateful Hot Reload, a new reactive framework, rich widget set, and integrated tooling.
  • Expressive and flexible designs with composable widget sets, rich animation libraries, and a layered, extensible architecture.
  • High-quality experiences across devices and platforms with our portable, GPU-accelerated renderer and high-performance, native ARM code runtime.

 

As a cross-platform mobile application development company, we are very excited about this solution because we can start using it immediately with our current apps. We don’t need to write our complete app in Flutter, we can simply add new Flutter-based screens to existing apps. Flutter is better than most of the cross-platform solutions we use today because it allows us, not only to build for two platforms but to make changes to the source code and see the UI updates in seconds, making the development process significantly faster.

 

If you are interested in learning more about Flutter, please reach out to schedule an informational meeting.

 

google-flutter-goes-beta

 

Mobile World Congress (#MWC18)

 

MWC is one of the biggest events on the mobile calendar. This year, more than in the past, the focus is going beyond our traditional understanding of Mobile Apps and pushing into the connected life or what MWC is calling “Intelligently Connected.”

 

Follow Shockoe to keep up to date on the key themes this year:

 

  • Artificial intelligence and machine learning (AI & ML)
  • Forthcoming 5G & LTE enablement
  • IoT smart city technology and edge computing devices
  • Big data and analytics
  • Technology in society and net neutrality
  • Consumer smartphone and tablet devices

SQL vs NoSQL, the ultimate database match

SQL vs NoSQL, the ultimate database match

In the blue corner, SQL stands with an arched back and titanium walker. In the red corner, NoSQL maintains steady movement and a toddler’s impatience.

Created in the early 1970s, SQL was the unrivaled choice for applications both large and small that were in need of storing, managing, and retrieving data. Deciding to implement a SQL-driven database was a no-brainer for developers and system architects because it was unmatched by its alternatives not only in reliability but also in its reporting capabilities. In recent years, however, SQL, the once unrivaled query language, is being replaced slowly but surely by the new kid on the block: NoSQL.

Let’s take a moment to evaluate and discuss the differences and similarities of these database models with a few areas of focus. For the sake of this discussion, we will use MongoDB as the NoSQL example and MySQL for the SQL example, but the assertions for NoSQL are not limited or specific to Mongo but also apply to numerous other NoSQL campers like MapReduce, Bigtable, Cassandra, and others. Many of these NoSQL technologies have similar capabilities and advantages, as well as weaknesses. Similarly, on the SQL/RDBMS side, we will reference MySQL, but the same ideas carry over for other solutions like Oracle, SQL Server, and others.

The Back Story
We first need a bit of backstory. We’ll begin by asking ourselves what SQL vs. NoSQL really means. To answer this we have to understand what a database and a database system are. Now, bear with me here, without trying to bore you to death, we may agree that a database is a system for storing and managing data and a database system is a program for managing databases. Got that? So next, the data in a database has to be organized in some way. The way in which various database systems choose to organize their data is referred to as a database model, and this is where SQL and NoSQL come in. On the surface, think of SQL as the relational model and NoSQL as the non-relational model. Diving deeper, we find that the relational model uses set theory and predicate logic, in which the data looks like it is organized in tables and columns, while the NoSQL model stores data as key-value pairs without any strict relation to each other. End of backstory; you’ve uncovered the plot, so now let’s jump straight to the three-round fight scene.

Ding!

Data scientists and architects assert that SQL is not out for the count, and I agree. Relational databases have a huge advantage compared to these wannabe newcomers primarily because they have excellent tooling, community, and support. The “why change what’s working?” question comes up pretty often when developers and system architects are faced with the decision of rolling out a data management tool. SQL offers two main advantages: first, it introduces the concept of accessing many records with one single command; and second, it eliminates the need to specify how to reach a record; e.g., with or without an index. Since most NoSQL databases lack the ability for joins in queries, the database schema generally needs to be designed differently and lends itself to some inefficiencies, giving SQL round one of this match by a small margin.

Popular NoSQL databases were pioneered by top Internet companies like Amazon, Google, LinkedIn, and Facebook to overcome the drawbacks of the relational model. The relational model is not always the best solution for all situations as it cannot meet the increasing growth of unstructured data.

NoSQL is better for unstructured data

You may have paused to ask yourself why some organizations have unstructured data. Well, social media posts and multimedia are only two examples of unstructured data, which should answer that question, but more importantly, since the world’s data is doubling every two years, companies now have a much greater need to not only store and accommodate unstructured data, but also to aggregate and use this data meaningfully. This is where the relational model takes a hit to the face giving round two to NoSQL.

Round three has three main focus; performance, planning, and price. Ideally, this is where the rubber meets the road in this decision. With NoSQL, instead of retrieving all the data

with one query, it is common to do several queries to get the desired data, but NoSQL queries are often faster than relational SQL queries so the cost of having to do additional queries may be acceptable. So if an excessive number of queries would be necessary and the company’s culture values performance, SQL or NoSQL may take the performance point for this round.

Planning is the second judge and it is very important, yet subjective. SQL databases have predefined schemas while NoSQL databases use a dynamic schema due to the nature of unstructured data. Therefore, SQL databases can be scaled vertically whereas NoSQL databases can be scaled horizontally. This means that to scale an SQL database, we simply increase the processing power of the hardware, while to scale a NoSQL database, we increase database servers in the pool of resources to reduce the load. Based on the organization’s planning culture, either SQL or NoSQL can take the planning point and this leave price as the final factor.

All companies make an effort to keep cost low so price an important factor. Most, if not all NoSQL databases are open source or very very low cost, which makes them very appealing. For the example, MongoDB, Couchbase, CloudDB, and Amazon’s Dynamo DB all allow very affordable implementations. On the other hand, among the major players in the SQL space, only MySQL (and its fork MariaDB) offers an open source solution. Both Oracle and SQL Server can be very expensive solutions due to the support they offer, which is usually readily available from their vendors or a fair number of independent consultants. NoSQL databases rely mainly on community support.

The Judge’s Decision
Depending on your vantage point or who you ask, some may agree that this match could have ended in a technical draw a few paragraphs prior, where unstructured data was a definite need and nothing else mattered. Another vantage point may have revealed that this match has ended in a TKO if no budget was allocated to database implementations and management couldn’t care less about database models, giving priority to an open-source-only solution. Ultimately, you decide who wins round three and therefore the match.

Despite your vantage point or decision, we all can agree that SQL and NoSQL serve very different purposes, with their own unique strengths and weaknesses; without them, developers and system architects would have been pulling their hair out trying to figure out how to utilize data locked away in filing cabinets to generate predictive analysis and graphs.

Ding!

 

Editor’s Note: 

Learn more about what our developers choose as their everyday tools and what our thoughts on these are:

Comparing React Native to Axway Titanium 

Kotlin: Three Reasons to Start Using it Today 

Node.js – Storing data with MongoDB

How to Choose the Right Software Development Firm

How to Choose the Right Software Development Firm

Thousands of companies have adopted enterprise mobile apps during a time when they were becoming popular and they wanted to “check the box” that they had an app. While most companies understand the benefits of mobile technology, unfortunately, some have failed to exercise due diligence when developing their app and selecting the proper vendor for their project. There are many great mobile development firms out there, but making sure you have a good fit for a particular project is the most important factor to consider when selecting a vendor. Let’s go through the checklist of items that should be considered any time your organization wants to develop an enterprise app.

1. Does the developer have previous experience with similar projects?

This is an important question to ask because the quality and agility of the project will be significantly better if the mobile dev team has completed a similar project before. For example, if you’re in the banking industry, look for a firm that has made banking apps before. They may have made other great apps for other industries, but that doesn’t always translate into a successful project in yours.

2. Does the vendor have a dedicated UI/UX team?

If the answer is no, immediately disqualify that vendor. Huge mistake companies have made was neglecting the end-user experience. The “it doesn’t matter if it looks that great because they have to use it anyway” line of thinking is counterintuitive with the premise of increasing employee work performance and satisfaction. A dedicated and experienced UI/UX team will make sure the app is intuitive. After all, a user interface is like a jokeif you have to explain it, then it’s not very good

3. Look at customer reviews.

Most mobile agencies, if they have been in the industry long enough, will have customer reviews. These can be found on sites like Clutch and GoodFirms and can provide great insight into how successful similar projects were and how easy it is to work with their team.

4. Get to know the team.

When selecting the right firm, get to know the team that will be assigned to your project. Typically a team will consist of a couple developers, a designer, and a project manager. It’s important to get to know these people to determine how easy it will be to work with them, and also to decipher if they are qualified to take on your project.

5. Look for someone who is concerned with the overall objective, not just the app.

Mobile agencies need to understand the “big picture” that the client wants to accomplish. Some developers may get caught up with the app development and making it really “sweet,” which is good, but clients don’t care about how cool the app is if it doesn’t address the business objectives it is supposed to help accomplish.

6. Don’t get hung up on price.

The saying that “you get what you pay for” has never been truer than in the mobile dev industry. A huge mistake that a lot of companies have made was selecting vendors who proposed the lowest prices. More often than not, those apps did not perform as desired and business goals were never realized. Make sure the mobile agency you select fits all the other criteria, and then discuss pricing.

7. Ask for a demo.

Even if the app is custom, always ask for a demo of an existing app that is similar to the project you want to be completed. This is the easiest way to determine the quality of the apps that could be developed for your company.

8. Ask a lot of questions.

This seems like a no-brainer, but failing to ask lots of questions was a common mistake made by companies that paid for apps during the initial mobile app frenzy. In an effort to save money by going with the cheapest vendor, these companies instead wasted money on apps that didn’t meet their needs. In the long run, they spent more on app development than they would have had they chosen the best agency for the job instead of the cheapest. Bring in all the internal stakeholders and come up with a list of questions to ask the vendor to make sure no details are left out, which could be detrimental to the project down the line.

9. Do they know your industry?

Mobile agencies that understand your industry and business are invaluable. Instead of just taking orders and accepting the requirements, look for a firm that can challenge your own ideas and provide insight that you may not have thought of before. If they are experienced, they should know plenty about how certain apps work within your business and be able to provide best practices for the project.

In conclusion, after taking everything on this list into consideration, you should be able to narrow down the mobile development firm that is right for your project. Even if your company has had a bad experience in the past, following these guidelines should help you avoid wasting money on an app that in the end does nothing to push the objectives of your business.

Ready to get to know the Shockoe team? Reach out to us and see if we’re a good fit for your next app development project. 

5 Ways Shockoe Supercharged Mobile Workflow

5 Ways Shockoe Supercharged Mobile Workflow

Here at Shockoe, we work on a variety of applications for clients around the world, so it’s not uncommon for our developers to work on more than one project at a time. It’s pretty standard that everyone, at some point or another, is involved in multiple builds for multiple devices. Multiple builds for multiple devices can get unwieldy, so we have crafted a few solutions to help keep our development smooth and efficient.

We supercharged our mobile workflow by:

1. Building a system to distribute our apps

Shockoe created a system for the app teams, including clients and mobile testers, to download and install the app right on their phones. But this isn’t enough on its own. Developers love to automate things, so along with our distribution system, we have been using Jenkins to automate the process of building the apps and uploading them to the website.

2. Using Jenkins to automate builds

We updated our Jenkins continuous integration system to automatically put new builds on our app distribution system whenever developers push new code. We needed a continuous integration system that was easy to maintain, but powerful and flexible enough to handle all of the projects we work on at any given time.

3. Using Gulp.js to automate building apps

The next hurdle we faced was being able to build a wide array of different mobile platforms and frameworks without overwhelming our developers. Every platform has its own unique build process. At Shockoe, we’ve started using Gulp.js to handle building each project, understanding that each coding language has its own set of unique requirements. Gulp acts as a task runner to allow developers to define and run repeatable build steps using Node.js. Gulp allows us to reuse a lot of code between different platforms, and it makes tracking down any build errors much easier because we know exactly which step failed. Using Gulp for our builds also means that the build process is portable. Any of our developers are able to perform builds and upload the apps in the event that our Jenkins computer ever stops working.

4. Automating the building and deployment of our solutions onto remote servers

After the process of building and deploying apps was finished, we moved on to automating our web projects. This was more straightforward than building an app. Again, we leveraged Gulp to remotely connect to the server that hosts the site, pull any new code changes, and finally build the site again. Automating this process also cut down on the number of errors made when deploying a site, thus avoiding simple mistakes like using a test configuration in a production environment.

5. Running automated tests and reporting the results

Earlier, I mentioned that part of the build process includes running automated tests on our projects. Jenkins has many tools and plugins to simplify this process. There are plugins to automatically launch an Android emulator or an iOS simulator when the tests start, and to close it after all the tests have finished. We chose Appium as our automated testing framework. Appium tests can tap, swipe, and type inside apps automatically. Automating the testing process makes it easier to test our solutions more often and more consistently. Each project in the Jenkins dashboard has a section for the test results of every build. This means you can see which changes were made in a build and whether those changes affected any of the project’s tests. When Jenkins does find a test that fails, it immediately notifies the team using Slack. With multiple developers often working on the same project, this step is helpful in finding bugs as soon as they are introduced.

To Summarize

By automating the process of delivering our apps to our stakeholders, we are able to iterate much more quickly on our products and cut down on the number of errors. Building our own website that allows us to control who can download our apps means we don’t have to worry about uploading our apps to different file share sites, or attaching them to an email and sending it to everyone who needs it. The flexibility of Jenkins means that our build computers can continually be upgraded or replaced if necessary. If our build is ever too slow, we can just add more computing power to our Jenkins setup.

Using Jenkins with Gulp allowed us to really speed things up. Since Gulp is powered by Node, our developers found it easy to jump right in and start extending the build process with new tasks like deploying and updating websites automatically.

The final step was to use the wide array of plugins for Jenkins to run automated tests, report the results, and notify the development team when something goes wrong. All of these steps helped our developers concentrate on what is really important: delivering a high-quality product without worrying about constantly building and sending apps.

 

Note from Editor:

If you’re interested in learning more about what our developers are using as everyday tools, check out these Blogs:

How to Document an Existing API Using Swagger

Debugging Titanium Applications using Safari Web Inspector

Kotlin: Three Reasons To Start Using It Today

Want to stay connected on all things mobile?

Sign up for the Shockoe newsletter and we'll keep you updated with the latest blogs, podcasts, and events focused on emerging mobile trends.

Shockoe Ranks No. 499 on the 2017 Inc. 5000 with Three-Year Sales Growth of 900%

Shockoe Ranks No. 499 on the 2017 Inc. 5000 with Three-Year Sales Growth of 900%

Richmond, VA – Shockoe, a leading disruptor in the design and development of advanced mobile solutions, is honored to announce that it has been ranked No. 499 by Inc. Magazine in the 36th annual Inc. 5000, an exclusive list of the nation’s fastest-growing private companies. This list represents the culmination of a review and ranking of one of the most important sectors in the American economy today – the entrepreneurs. It’s an honor to be named in a list where many of today’s well-known companies gained their first national exposure as honorees of the Inc. 5000, such as Microsoft, Zappos, Jamba Juice, Pandora, Timberland, LinkedIn, Yelp and Zillow.

We are tremendously proud of the work we’ve accomplished in the past few years and feel honored to be included amongst an exclusive and impressive list of growing companies on the Inc. 500,” says CEO Edwin Huertas of Shockoe.com. “Our growth over the years can be directly attributed to an incredible team and their ability to create innovative solutions for today’s ever-changing market coupled with the capacity to solve the most pressing challenges organizations face.”

The 2017 Inc. 5000, unveiled online at Inc.com and with the top 500 companies featured in the September issue of Inc. (available on newsstands August 16) is the most competitive crop in the list’s history. The average company on the list achieved a mind-boggling three-year average growth of 481%. The Inc. 5000’s aggregate revenue is $206 billion, and the companies on the list collectively generated 619,500 jobs over the past three years. Complete results of the Inc. 5000, including company profiles and an interactive database that can be sorted by industry, region, and other criteria, can be found at www.inc.com/inc5000.

About Shockoe:
Shockoe is a leader in the development of advanced mobile solutions focused on increasing sales, end-user experiences and employee productivity. Since our founding in 2010, our emphasis on today’s mobile consumer and end-user have helped us grow into a global consulting firm with a unique combination of mobile strategy, experience design, development, and integration. Our solutions have strong returns on investment, deliver excellent user experiences, and adhere to the best practices in security and reliability.

Want to stay connected on all things mobile?

Sign up for the Shockoe newsletter and we'll keep you updated with the latest blogs, podcasts, and events focused on emerging mobile trends.