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

Event Recap: Southeastern Credit Union Conference

Event Recap: Southeastern Credit Union Conference

 

Mobile Apps at SCUCE

Last week we had the pleasure of attending the Southeast CU Conference & Expo. SCUCE is one of the largest credit union conferences in the country and is a great opportunity to interact with and exchange innovative ideas with credit union leaders from across the country. One of the main goals of this annual conference is to discuss the latest technologies, applications, and resources available to credit unions to improve their operations and efficiencies and better serve their members. At Shockoe, we are committed to innovation through mobile technology and customer-centric solutions, so we were excited to participate in this event and share our experience developing the mobile app for Virginia Credit Union.

 

Why is mobile technology for credit unions now more important than ever?

There is no question: mobile technology has become as ubiquitous as the wallet in one’s pocket or purse. With 95% of Americans carrying a cell phone (77% of which are smartphones), credit unions should be feeling the pressure to deliver great app experiences to their customers.

According to the BBC, in 2011, 22 million UK adults turned to their cell phones to manage their finances, and this number is expected to rise to as many as 35 million by 2023 — a whopping total of 73% of the country’s population.

Here in 2018, Gen-X is increasingly impacting the direction of the U.S. workforce. More dollars are being invested with financial institutions, and younger users are looking to maintain greater oversight of their finances in the way they know best — mobile interfaces. In fact, The CACI estimates that by 2019 mobile will overtake traditional online banking. A shockingly rapid change since 2013 when approximately only 50% of online users opted for mobile as their platform of choice versus web.

 

On the side of change

In addition to rethinking their IT strategies, Credit Unions need to prepare for a shift operational strategies as well. Branches will play a decreasingly relevant role in money management for the active workforce — money that could be shifted to embracing security, IT infrastructure, and mobile strategy that keeps banking operations lean and effective.

It’s clear that today’s digital-centric credit union consumers have higher expectations of their institutional providers. To appeal to these consumers, businesses will need to find innovative approaches to digital customer engagement. Custom mobile apps allow for a tailored, personalized, customer-centric experience that is unique to the brand. Pair this with customized employee productivity apps, and the entire operation works seamlessly to meet customer needs and build brand loyalty. The credit unions that are able to adopt this “outside-the-box” thinking will be more likely to succeed in this new, highly competitive consumer landscape.

 

How can Shockoe help your credit union?

In working with a local credit union, Virginia Credit Union, (one of the 55 largest Credit Unions in the country), we’ve conducted significant discovery to determine what members expect out of their banking app. VACU approached us to help their customers integrate a credit card management option and improve their overall mobile application. VACU was using Fiserv as their core banking system and quickly realized that they were limited in what they could do with their mobile technology. We learned at SCUCE that this is a very common theme at most credit unions. As credit unions outgrow the “out of the box” solution, they search for a more tailored way to connect with their members to uphold customer loyalty and provide a seamless mobile banking experience. That’s where Shockoe comes in. We augment your development teams and partner together with your core banking system to provide you with a custom app carefully designed to fit your members’ needs.

Our connections at SCUCE confirmed our assertions about the importance of custom mobile apps, and we’d love to continue to share our knowledge and expertise with the greater credit union community. Whether it’s helping with API strategy, development, security, or UX/UI design – we’re here to help find innovative approaches to attracting and retaining customers and improving the day-to-day employee experience. At Shockoe, we strongly believe that the credit unions that embrace custom mobile apps will have customers that are more engaged and more loyal, allowing these credit unions to thrive in a highly competitive environment. If you’re interested in exploring how custom apps might be able to help your credit union, give us a call to chat about how we can help!

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 on 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

Comparing React Native to Axway Titanium

Comparing React Native to Axway Titanium

Here at Shockoe we often use cross-platform tools to build our apps. Using a cross-platform tool allows us to have one code base for apps that run on multiple platforms. There will be some platform specific code, but most things can be shared. Our cross-platform tool of choice is Axway Titanium. It used to be that cross-platform tools heavily leveraged WebViews. Tools like Cordova (ex PhoneGap) allow the developer to write a mobile website using HTML, CSS, and JavaScript. Then PhoneGap handles showing this content to the user inside of a native WebView. Instead of the WebView approach, Titanium gives you a JavaScript context and provides a bridge that handles interactions between the JavaScript environment and native components. Titanium stood out because it actually interacted with native components. But now Titanium is not the only framework out there that takes this approach. A couple years ago Kyle took an early look at React Native. Let’s take another look and see how React Native has come along.

Getting Started

Start off by heading over to the React Native Getting Started page. They offer two options: Quick Start and Building Projects with Native Code. I have not tried the, now default, Quick Start option. Several documentation pages refer to needing to “eject” your application if it was created from the Quick Start. For that reason alone I have only used the Building Projects with Native Code option.

There are a few dependencies to install, but the guide walks you through what you need. You will need NodeJS and the watchman package for observing changes. You will also need to install the react native cli. Additionally, you will need Xcode if building for iOS and Android Studio if building for Android.

Once you’ve got the dependencies installed you create a new project with the CLI:
react-native init AwesomeProject

Running the App

With no changes to the code base, you can immediately build the app you just created. In a Titanium project, all builds are handled through the Axway Appcelerator CLI or Axway Appcelerator Studio. This is not the case with React. It seems you can only build to an iOS simulator, Android emulator, or Android device with the React Native CLI. To do this you use either:
react-native run-ios
To target iOS simulator. Or:
react-native run-android
To target an Android device or emulator.

The options provided with these commands are a little lacking compared to the options with the Axway Appcelerator CLI. In my time with React Native, every simulator build chose the iPhone 6 simulator. I could not find an option to specify a different simulator with the CLI. Additionally, the CLI does not handle multiple connected Android devices well. You need to only have a single connected Android device or running emulator.

So how do you target other iOS simulators or build to an iOS device? Open Xcode! From there you use the same build options that a native developer would use. This is a huge difference from Titanium that basically discourages the use of Xcode for anything but building native modules. If you’ve never done native iOS development this can be a little daunting at first. It’s simple enough to find the play button and drop-down to select your build target. But what if you want to do an adhoc distribution build? Fortunately, there are plenty of resources out there for learning Xcode.

How about Android builds? This is an area that I am not as familiar with. Because the React Native CLI is capable of building to a device, I haven’t tried to build the project with Android Studio. I have generated a signed APK. The React Native documentation has a guide, but it comes down to using gradle.

Editing the App

React Native does not provide an IDE like Axway Appcelerator Studio. The documentation does suggest taking a look at Nuclide. Nuclide is a package for Atom that claims to setup an environment for developing React Native. I found I wasn’t taking advantage of its features, so I uninstalled it after a couple days in favor of just Atom.

So you can open the code in a text editor, where do you go from there? With a Titanium project, at least an alloy one, the entry point is alloy.js. From there the index controller has loaded first automatically. React Native provides entry points at index.android.js and index.ios.js. From there you can load whatever components you wish. The simplest thing to do is to edit some of the text provided with the sample project. Once you’ve made an update you can easily see your changes without rebuilding your app!

Axway Titanium provides a live view feature to see your app update as code changes. React Native offers a similar feature. On simulator you can press command + R to reload the code from the React Native packager. On an android emulator you can achieve the same thing by tapping R twice. Reloading can also be accessed from a built-in developer menu! To access the developer menu simply shake your device. You will see options to reload, enable remote JS debugging, enable live reload, and more.

Debugging Your Code

Axway Titanium attaches a console to builds made directly to a device, emulator, or simulator. The React Native process ends as soon as a build is installed and does not attach a console. Instead, you can enable remote debugging through the developer menu and debug your app in Google Chrome. You do not see a DOM representation of the app, but you do get access do the console and debugging tools! The debugging is done over TCP, so you don’t need to have built on a device connected to your computer. Inside the developer menu, you can change the URL used for remote debugging so you can debug as long as the device and machine running Google Chrome are on the same network.

Moving Forward

This has only been a brief look at getting started with React Native. In the future, I would like to revisit this topic to discuss more configuration, component driven design, and interacting with native code. React Native is very young, but it has come a long way in a short period of time. I am very excited to see how it matures as a cross-platform framework.

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.

Kotlin: Three Reasons To Start Using It Today

Kotlin: Three Reasons To Start Using It Today

With the announcement at Google I/O 2017 that the Kotlin programming language will be officially supported as a first class citizen for the Android framework, there’s been a lot of talk around what Kotlin is and how it compares to Java. This post highlights the reasons why our development team at Shockoe feels that Kotlin is the wave of the future and why Android developers should start adopting it.

What is Kotlin?

Kotlin is a statically typed programming language that runs on the Java Virtual Machine (JVM). It’s a multi paradigm language that contains elements of object-oriented programming that you’d see in languages like Java and elements of functional programming like what you’d find in JavaScript.

Why should you start using Kotlin?

Here are our top three reasons why you should jump in:

#1 Easily integrated into your mobile stack

Kotlin code is compiled into the same bytecode that your regular Java programs are and uses the Java Virtual Machine (JVM) to execute that code.
This means that Kotlin still has access to all the same libraries and frameworks available to you in the Java world with the addition of those from the Kotlin standard library. This also allows Kotlin and Java code to run concurrently with one another. Java classes calling methods in Kotlin classes or libraries and vice versa. This can even be done in the same file. Take this example from a library that handles unit conversion:

fun celsiusToFahrenheit(value: Double): Int = Math.round(value * 1.8 + 32).toInt()

Here we have a function that takes a Double parameter and returns an Int. However, we want to use the java.lang.Math class to round as this feature doesn’t exist in Kotlin. So we round to the nearest place and call a method from the Kotlin Double class to convert the result into an Int.

This duality of execution allows developers to easily convert their existing Android projects from Java to Kotlin or to simply add new features written in Kotlin, without converting previously written code.

Additionally, Kotlin has an option to compile into JavaScript which is compatible with the standard JavaScript specifications like Asynchronous Module Definition (AMD), CommonJS, and Universal Model Definition (UMD). This allows developers to share code written in Kotlin to other environments like Node.js or even to cross platform environments like to Appcelerator’s Titanium framework.

#2 Multi-paradigm language

A lot of the developers from Shockoe come from multiple different backgrounds. Some started with Java and transitioned into writing JavaScript while others started with JavaScript and have since learned about other languages.

Kotlin adds a lot of functional features to the object-oriented nature of Java. I realize that Java 8/9 adds similar features but this post is specific to the Android platform.

These features coupled with improved/sugar syntax lead to a much more easily read codebase. I won’t go over all the features but some of the most prominent ones are higher order functions, null safety, better typing with type inference, and much less boilerplate code.

These features, in particular, allow a developer to write much cleaner code and a lot less of it. Here’s an example of some Java code to perform and common action – filtering a list:

 ArrayList getAllEvenValues(int[] values) {
    ArrayList filtered = new ArrayList<>();
    for (int i : values) {
       if (i % 2 == 0) {
          filtered.add(i);
       }
    }
    return filtered;
 }

This isn’t too terribly much but it can quickly spiral out of control as you start adding more complexity. The Kotlin equivalent would look like:

fun getAllEvenValues(values: List): List = list.filter { it % 2 == 0 }

Yep, that’s it. There are many more operators that can be appended to the end of that, for instance if you wanted to map the results to strings and return the string list you just make one minor change.

fun getAllEvenValues(values: List): List = list.filter { it % 2 == 0 }.map { it.toString() }

#3 Official Support From Google

No brainer here, right? However, the announcement from Google I/O 2017 is a huge deal for the language. In addition to the benefits of Kotlin over Java such as those detailed above, Kotlin will now have full support in Android Studio and within the Android ecosystem. JetBrains and Google are working together to continue to support the language into the foreseeable future.

Kotlin is by no means meant to replace Java. However, it will allow for better apps to be written using a much more modern and architected language that keeps developers in mind.

Conclusion

Now is a great time to jump into Kotlin and to start writing your Android apps with it. It will lead to better productivity for your mobile team, as they’ll be writing less code – which will be more readable and therefore easier to maintain.

Additionally, if you’re a multi-platform development team, the cross compilation into JavaScript is a great addition as you can easily create tools that work within frameworks for both languages.

Then there’s also the similarities between Kotlin and Swift as is highlighted here. This helps  bridge the gap between iOS and Android development teams.

Additional Resources

Official Kotlin Documentation

Sample Kotlin App

Kotlin Android (Layout) Extensions

Anko – Library with many Kotlin helper tools for Android

Kovenant – Promises for Kotlin

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.

Virtual, Augmented and Mixed Reality… Confused Yet?

Virtual, Augmented and Mixed Reality… Confused Yet?

There are exciting new worlds being created, recreated and explored as we speak. There are digital worlds being developed from the inspirations of Earth and beyond. For those of us not able to travel to places like the polar ice caps, the Sistine Chapel, Rome, the Pyramids of Egypt, Mars, or other places we may not be able to visit in our lifetime, this is our chance. Now, we have the opportunity to visit them from the comforts of our very own homes.

Our mobile enterprise company, Shockoe.com has recently branched out into the brave new world of Virtual Reality (VR). In this ambitious new venture, there are many things to consider. First, let’s break down the different branches of the digital realities.

VR provides the user with a digitally created 360 degree world using a type of headset, whether it’s utilizing Google cardboard, an Oculus or one of the many other options of headset viewers. Augmented Reality (AR) uses our mobile devices and overlays digital images over physical reality (ever heard of Pokemon Go)? Lastly, and my favorite, there’s Mixed Reality (MR).

MR might be such an advanced technology, that we likely won’t see this catch on until VR and AR are more of a regularity. MR is the ability to use virtual reality inside of our physical world. For instance, a doctor performing surgery on a patient could use a virtual magnetic resonance imaging (MRI) or X-ray scanner over their patient, providing them with an accurate view inside their patient’s body. Mind-blowing, right?

Now that you have an idea of the different realities being created, let me tell you that there is nothing more exciting than having the opportunity to design the User Experience (UX) and User Interface (UI) for these exciting realities. When starting the conversation of UX for VR, it’s easy to get a little carried away. The possibilities seem endless (because they are), which is why it’s important to focus on what’s best for the user, what makes the most sense for the user to do in order to see and navigate our experiences. What does the client want to provide their users?

These questions are seemingly simple, yet necessary. A UX/UI designer needs to know what type of VR they are designing for. Is it for a headset alone, headset with camera sensors, or headset with gloves? What are the limitations of this experience? How far can the UX/UI designer push these limitations while still maintaining a fulfilling, yet positive user experience? What can I designer do to keep users returning to their fascinating VR experiences and even share them with others?

shockoe_vr_coneoffocusUsers with solo headsets can only use their Field of View (FOV) or Cone of Focus to make their selection, not their hands. While this might seem limiting, it’s not. Keep in mind that this is VR, where the user can turn in any direction they choose and explore a new world by just putting on a headset. Making a selection through vision is quite simple. A UX designer could use a countdown, various loading animations, or status bars. They can even invent something totally new and intuitive that hasn’t been thought of yet.

Making a selection is one thing, navigating these new worlds is another. There are a lot of different things to consider when navigating in VR. For one thing, it’s somewhat similar to navigating our physical world in terms of our FOV. We all have our own, some of us more or less than others, and the Cone of Focus is how designers segment the FOV.

The UX designer should focus the user’s primary actions within the initial area of vision. When we look directly forward, by just moving our eyes we can see approximately 90 degrees within our central vision. Everything outside of that is our far peripheral vision and should be designed accordingly by placing only secondary and tertiary user actions within these areas of vision, such as motion cues or exit options.

These are extremely important limitations to know when designing the UX for VR experiences. These degrees of vision define how the UX should be envisioned and implemented. Without making the user work too hard to explore their new digital surroundings, the UX designer must take into account the Cone of Focus for all primary actions without taking away from the extraordinary experience of VR. Thus, making one consider the visual placement of UX design by measurements of FOV degrees throughout the app.

While all of this information may seem overwhelming, it is also very, very exciting. Designing UX and UI in 360 degrees is a phenomenal opportunity to learn, adapt and innovate in this amazing new digital age. At Shockoe.com, we are on the edge of our seats with excitement about being able to provide our clients with the intuitive experiences their users want through innovative technology that VR offers.

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.

Google Glasses are here

It’s the innate nature of Google to constantly push the envelope, advancing technology and making life easier for us all. With its many years in contemplation and development, Larry Page and Sergey Brin have finally brought a long term dream to reality.

In addition to the self-driving car, the Google (x) department has developed yet another product that proves to challenge our idea of what the future may hold, with all of its glorious possibilities.

Upon presenting its techno-stylish and highly futuristic augmented reality glasses, Google has confirmed that Project Glass is in full effect. What the Google Glass is offering is certainly a spectacle, (no pun intended).  Almost reinventing the meaning of a hands-free device, the glasses gives users the same full range of activities that ordinary smart phones provide and definitely opens up new possibilities that other devices are just not capable of. Using transparent and interactive imagery that is positioned in your field of vision, Google Glass takes information that is usually accessed via search engine and media sources, and places it directly in your range of sight. Now, weather updates phone calls, and even Google Maps, are easily accessible with Google’s cool gadget.

What could that mean for app development? A whole new range of apps is sure to spawn from this creation, entirely changing how we interact with the apps on our mobile devices. Imagine being able to navigate through unfamiliar cities without having to check to see if you are going the right way because the directions are right in front of  you. Entire virtual worlds could be blended effortlessly into our range of sight, creating the ultimate gaming environment, rather than viewing them through the screen of a smartphone, an entire virtual world would be.

Although highly anticipated, it is still in its beta testing phase, so we can only imagine the possibilities that the Google Glass may bring. For this mobile device I envision an entirely new frontier as far as app development goes, but until then my guess is as good as yours.