Giter VIP home page Giter VIP logo

realm-inventory's Introduction

Inventory


A Realm Mobile Platform Example Application


Intro

This app is a simple implementation of an idealized inventory tracking system designed to show how transaction safe counters can be implemented in a mobile application.

Installation

Prerequisites

This app uses Cocoapods to set up the project's 3rd party dependencies. Installation can be directly (from instructions at the Cocapods site) or alternatively through a package management system like Homebrew.

Realm Mobile Platform

This application demonstrates features of the Realm Mobile Platform and needs to have a working instance of the Realm Object Server available to make tasks, and other data available between instances of the Inventory app. The Realm Mobile Platform can be downloaded from Realm Mobile Platform and exists in two forms, a ready-to-run macOS version of the server, and a Linux version that runs on RHEL/CentOS versions 6/7 and Ubuntu as well as several Amazon AMIs and Digital Ocean Droplets. The macOS version can be run with Inventory right out of the box; the Linux version will require access to a Linux server.

3rd Party Modules

The following modules will be installed as part of the Cocoapods setup:

  • RealmSwift The Realm bindings for Cocoa/Swift

  • Realm LoginKit A Realm control for logging in to Realm servers

  • BarcodeScanner an elegant barcode scanner module for iOS

  • Eureka a formbuilder for iOS in Swift by xmartlabs

  • ImagePicker for selection of photo library images by Hyper.no

  • ImageRow an image row component for the Eureka formbuilder

  • PermissionScope permission management dialog by Nick O'Neill

Preparing the Realm Object Server

The Inventory application can be used with any version of the Realm Object Server (Developer, Professional or Enterprise). The Inventory app needs to be able to set permissions on the Realm used by the app - this must be done by a Realm user that has permission/rights to administer the server. This could be the first account you set up as part of the Realm Object Server installation, or any account that has the admin bit set.

You can determine which accounts have admin rights by logging in to the Realm Object Server Dashboard: ROS Dashboard User Listing

You create new users (and give them admin rights) on this screen.

You can set the admin rights for existing users by clicking-through to the user's profile page and checking the "can administer this server" checkbox: ROS Dashboard User Listing

Once you have created or selected an admin user to use, you can proceed with compiling and running Inventory.

Compiling & Running the Application

Before attempting to compile the project, install all of its dependencies using Cocoapods by invoking pod install. This is done by opening a Terminal window and changing to the directory where you downloaded the Inventory repository. In this main directory is a Folder called InventoryApp that contains Podfile needed by CocoaPods as well as the application sources.

This process will create a Pods directory which contains all of the compiled resources needed by the app, along with an Xcode xcworkspace file which you will open and work with instead of the Inventory.xcproject file when building/running this application.

Once the cocoapods have been retrieved, open the Inventory.xcworkspace file and press build. The app should compile cleanly.

First Login

As mentioned above, the first login to the Inventory app needs to be by a user enabled with administrative privileges on the Realm Object Server. This is to enable a global Read/Write permission on the shared Realm that is created by the application.

Adding Users

Adding users can be done either via the Realm Dashboard, or by adding users using the Inventory the app itself from the login screen.


Navigating Inventory

The Inventory app is a simple "tab bar" application - that is to say it supports a number of main views that are accessible at all times:


Tab Bar
In this case, Inventory is very simple and supports just the main products list (in essence the "inventory" managed by the app) and a settings screen on which you can manage your profile or log out of the app.

The app does support several other detail screens that are explained along with the main applications screens these are:

  • Product view
  • Settings view
  • Barcode scanning view for finding existing or creating new products)
  • Product Detail view for seeing the details of existing products
  • Product entry view for entering the details of a newly created product

App Permissions

Inventory shows how do use some specialized features of Realm and is not meant to be a full-blown inventory system; never the less is can be used my multiple users as a way to try out the safe counters and sync'd transactions demonstrated here. In order to enable multiple users Inventory implements a top-level Realm named "Inventory" that has its permissions set to "* RW" which in Realm's permission structure is a wildcard read/write permission.

In order to set up the app fo multi-user access, the first user to log in using this app needs to be a Realm admin user -- that is usually the user ID of the person who set up the Realm object server; it can also be any other user who has been granted admin permissions via the Realm dashboard. See Preparing the Realm Object Server, above.

The Products View


Product Listing

Products can be viewed across a number of dimensions - tapping the sort menu in the upper-left corner of the products view will bring up the sort options menu, and tapping the arrow immediately to the right will change the sorting direction


The Barcode Scanner & Product Detail Entry

In order to use the barcode scanner built in to Inventory, you must give permission for the Inventory app to access the iOS device's camera:

Camera Permission Request

However, if you are running this application in the OS simulator under Xcode, there is no camera to access. You will need to enter product UPC codes by hand when using the simulator:

Simulator Warning

New Product Creation

A new product can be entered into Inventory in 2 ways:

  • tapping the "+" button on the product listing screen. This will bring up an empty form that can be filled in


  • Scanning a barcode - if the (UPC) scan code is not currently represented in the products:

Barcode Scanning

If the product is not yet in the system you are given the option to add it:

Scancode Not Found

Adding a product image to your inventory record:


If a product you scanned is already in the inventory system, it will be displayed:

Successful Scan

Other Ways to Search

The main products view also supports a search bar that will allow search to be done for any of the product item fields.

Adding/Removing Inventory

You can add to or remove items from inventory for given product on it's detail screen:


The counter and button will indicate the number to be added or subtracted, pressing the button will write the transaction to the Realm and update all the counters visible in the app live and in real-time (and if your app is online in the views other other users as well).


Settings / Profile


Application Architecture

Tracking Additions and Subtractions From Inventory

One of the interesting aspects of a mobile application is the fact it -- to be truly useful -- needs to work both online and offline. Our inventory application as an additional constraint: it needs to support changing quantities inside the application in a consistent and mathematically correct way regardless of how many users are accessing or updating the items.

While this may seem self-evident, supporting arithmetical atomicity is tricky. Realm supports 2 different ways of ensuring that transactions (additions or subtractions) from inventories: Lists and Counters.

Method 1: Lists

TBD: show how adding up the transactions in a list will yield a consistent result w/out the use of atomic counters.

Let's look at a transaction log for a few hypothetical products:


Here we see there several products - (product ID's 1,2 3 and 5) each with several additions or removals (sales) from the inventory.

If we highlight just the activity for product 1, we can see quickly where this goes - adding up the amounts of all the transactions tells use the current quantity on-hand of product #1. This is a very safe way to implement a transaction safe counting system. It also has the ability to allow us to create a rich system where we can add more color (metadata) to our inventory system (like the ID of who sold what, what store or region did gets credit for a sale, etc).


Which comes down to: 100+(-8)+(-20)+10+(-50)+(-15) or a total of 17 on hand.

Method 2: Counters

Realm also supports access safe counters - This method will be described in the next update to this demo.

Models

The Inventory app consists of two basic models - a Product:

class Product : Object {
    dynamic var id = ""                 // this should be something that's universal, like a UPC
    dynamic var creationDate: Date?
    dynamic var lastUpdated: Date?
    dynamic var productName = ""
    dynamic var productDescription = ""
    dynamic var image : Data? // binary image data, stored as a PNG
    var amount: Int {
        get {
            return self.quantityOnHand()
        }
    }
    let transactions = List<Transaction>()
    // :
    // more class methods
    // :
} // of Product

...and a Transaction:

class Transaction : Object {
    dynamic var id = NSUUID().uuidString    // every transaction is unique but the person doing it, the products and amounts, of course are not
    dynamic var transactionDate: Date?
    dynamic var transactedBy = ""           // a Realm SyncUser.identity
    dynamic var productId = ""              // the product this refers to
    dynamic var amount = 0                  // positive = inventory addition, negative == sale or inventory reduction

    // Initializers, accessors & cet.
    override static func primaryKey() -> String? {
        return "id"
    }
}

The Product class is a minimalistic, idealized implementation of what a product in an inventory system might look like. The transaction class tracks all the required info about an addition to or sale from the inventory for any product. The key things to note in the Product class are:

  1. We use a Realm list property to be able to find all of the transactions (sales, and inventory replenishments) that are booked against this product, and

  2. The "amount" property is not actually stored in the Realm itself but is a synthesized property whose getter uses a class method to get the current quantity on hand for a given product.

Interestingly there a number of ways you could do this. For example, you could get a list of all of the transactions registered against a product, use Swift's map function to extract the transaction amounts and then sum the up using reduce:

func quantityOnHandUsingMapReduce() -> Int {
    let realm = try! Realm()
    let transactions = realm.objects(Transaction.self).filter("productId = %@", self.id).map{$0.amount} // get all the amounts for the product as an array
    return transactions.reduce(0, +) // now sum them to get the QoH
}

This is a perfectly valid way to do these kinds of aggregated calculations, but a better way uses Realm's built-in aggregation functions to do math for us:

func quantityOnHand() -> Int {
    let realm = try! Realm()
    let transactions = realm.objects(Transaction.self).filter("productId = %@", self.id)
    return transactions.sum(ofProperty: "amount")
}

The result of the use of the transaction list and the synthesis of the amount on hand using Realm's aggregation functions enables multiple users to use an app like Inventory and not worry about collisions or math errors and is very efficient.

For this small demo app we've implemented just add/subtract from the product count, but of course one can add any number of such multi-user safe operations. The implementation file here includes a quantitySold() function and the suggested outlines for functions to determine quantities sold across date ranges as well.

Contributing

See CONTRIBUTING.md for more details!

This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].

License

The Realm Inventory is distributed under the terms of the Apache License analytics

realm-inventory's People

Contributors

dhmspector avatar ianpward avatar tgoyne avatar timoliver avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

realm-inventory's Issues

Resize pictures to a sane size

Pictures taken from the library or camera are full size - resize these to something sane before persisting them into the Realm

Support shadow values when editing product in Detail view

right now faux real-time writes are happening (and are visible to anyone seeing a record either in the main listing or at the detail view thanks to object and collection updates) because the field level editing is wrapped in a Realm write block (making the "save" button in the form a little superfluous) -- need t support shadow field s that are then persisted on the actual call to the savePressed action.

Move Search bar to left side of UINav Header

Search bar is too skinny - move to left sodded of nav controller; find way to reclaim more space (maybe combine the scan and add buttons into one action (i.e., a pop-over "scan or add...")

Fatal Error on Login

2017-03-13 14:29:47.186 Inventory[38294:3579279] Sync: Opening Realm file: /Users/ianward/Library/Developer/CoreSimulator/Devices/9BDACF71-A578-4B7F-9E96-B42A7B3F1AA9/data/Containers/Data/Application/7EAD9D29-41B3-47AC-9D71-F1F7C6091B1C/Documents/realm-object-server/9cd46da879fe42d719b222cce674ae25/realm%3A%2F%2F138.68.226.227%3A9080%2FInventoryDemo
2017-03-13 14:29:47.190882 Inventory[38294:3579253] [] __nwlog_err_simulate_crash simulate crash already simulated "nw_socket_set_common_sockopts setsockopt SO_NOAPNFALLBK failed: [42] Protocol not available"
2017-03-13 14:29:47.191262 Inventory[38294:3579253] [] nw_socket_set_common_sockopts setsockopt SO_NOAPNFALLBK failed: [42] Protocol not available, dumping backtrace:
        [x86_64] libnetcore-856.30.16
    0   libsystem_network.dylib             0x0000000111401666 __nw_create_backtrace_string + 123
    1   libnetwork.dylib                    0x00000001116df006 nw_socket_add_input_handler + 3164
    2   libnetwork.dylib                    0x00000001116bc555 nw_endpoint_flow_attach_protocols + 3768
    3   libnetwork.dylib                    0x00000001116bb572 nw_endpoint_flow_setup_socket + 563
    4   libnetwork.dylib                    0x00000001116ba298 -[NWConcrete_nw_endpoint_flow startWithHandler:] + 2612
    5   libnetwork.dylib                    0x00000001116d5ae1 nw_endpoint_handler_path_change + 1261
    6   libnetwork.dylib                    0x00000001116d5510 nw_endpoint_handler_start + 570
    7   libdispatch.dylib                   0x000000011117e978 _dispatch_call_block_and_release + 12
    8   libdispatch.dylib                   0x00000001111a80cd _dispatch_client_callout + 8
    9   libdispatch.dylib                   0x0000000111185e17 _dispatch_queue_serial_drain + 236
    10  libdispatch.dylib                   0x0000000111186b4b _dispatch_queue_invoke + 1073
    11  libdispatch.dylib                   0x0000000111189385 _dispatch_root_queue_drain + 720
    12  libdispatch.dylib                   0x0000000111189059 _dispatch_worker_thread3 + 123
    13  libsystem_pthread.dylib             0x00000001115514de _pthread_wqthread + 1129
    14  libsystem_pthread.dylib             0x000000011154f341 start_wqthread + 13
2017-03-13 14:29:47.195 Inventory[38294:3579279] Sync: Connection[2]: Session[1]: Received: ALLOC(server_file_ident=3540987436341881894, client_file_ident=3, client_file_idennot processed.
t_secret=9217583544208153330)
2017-03-13 14:29:47.206 Inventory[38294:3579279] Sync: Connection[2]: Session[1]: Sending: IDENT(server_file_ident=3540987436341881894, client_file_ident=3, client_file_ident_secret=9217583544208153330, scan_server_version=0, scan_client_version=0, latest_server_version=0, latest_server_session_ident=0)
change notification: SyncPermissionChange {
	id = 23027C33-FCBF-4773-9E25-4113EA4D64F4;
	createdAt = 2017-03-13 21:29:47 +0000;
	updatedAt = 2017-03-13 21:29:47 +0000;
	statusMessage = (null);
	realmUrl = realm://138.68.226.227:9080/InventoryDemo;
	userId = *;
	statusCode = (null);
	mayManage = 0;
	mayRead = 1;
	mayWrite = 1;
}
2017-03-13 14:29:47.228 Inventory[38294:3579279] Sync: Connection[3]: Session[1]: Starting session for '/Users/ianward/Library/Developer/CoreSimulator/Devices/9BDACF71-A578-4B7F-9E96-B42A7B3F1AA9/data/Containers/Data/Application/7EAD9D29-41B3-47AC-9D71-F1F7C6091B1C/Documents/realm-object-server/9cd46da879fe42d719b222cce674ae25/realm%3A%2F%2F138.68.226.227%3A9080%2FInventoryDemo'
2017-03-13 14:29:47.228 Inventory[38294:3579279] Sync: Connection[3]: Resolving '138.68.226.227:9080'
2017-03-13 14:29:47.229 Inventory[38294:3579279] Sync: Connection[3]: Connecting to endpoint '138.68.226.227:9080' (1/1)
2017-03-13 14:29:47.233 Inventory[38294:3579279] Sync: Connection[3]: Connected to endpoint '138.68.226.227:9080' (from '172.20.20.107:64982')
2017-03-13 14:29:47.246 Inventory[38294:3579279] Sync: Connection[3]: Session[1]: Sending: BIND(server_path='/InventoryDemo', signed_user_token_size=561, need_file_ident_pair=0)
2017-03-13 14:29:47.246 Inventory[38294:3579279] Sync: Connection[3]: Session[1]: Sending: IDENT(server_file_ident=7583587558240369835, client_file_ident=1, client_file_ident_secret=4385820009941347054, scan_server_version=2, scan_client_version=2, latest_server_version=2, latest_server_session_ident=7304316190108397069)
not processed.
change notification: SyncPermissionChange {
	id = 23027C33-FCBF-4773-9E25-4113EA4D64F4;
	createdAt = 2017-03-13 21:29:47 +0000;
	updatedAt = 2017-03-13 21:29:47 +0000;
	statusMessage = (null);
	realmUrl = realm://138.68.226.227:9080/InventoryDemo;
	userId = *;
	statusCode = (null);
	mayManage = 0;
	mayRead = 1;
	mayWrite = 1;
}
succeeded.
change notification: SyncPermissionChange {
	id = 23027C33-FCBF-4773-9E25-4113EA4D64F4;
	createdAt = 2017-03-13 21:29:47 +0000;
	updatedAt = 2017-03-13 21:29:47 +0000;
	statusMessage = Modified the default permissions for the file at path /InventoryDemo to grant read-write access.;
	realmUrl = realm://138.68.226.227:9080/InventoryDemo;
	userId = *;
	statusCode = 0;
	mayManage = 0;
	mayRead = 1;
	mayWrite = 1;
}
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

Add UPC codes to Readme

Can we add some UPC codes to the Readme since we will be running this in Xcode for the most part?

Fatal Error when Clicking the Settings tab

start_wqthread + 13
2017-03-14 11:29:32.588 Inventory[42484:3812476] Sync: Connection[3]: Session[1]: Starting session for '/Users/ianward/Library/Developer/CoreSimulator/Devices/9BDACF71-A578-4B7F-9E96-B42A7B3F1AA9/data/Containers/Data/Application/76BA9D9D-9088-43AF-81D0-EA745FAF679F/Documents/realm-object-server/e25597ef88065c8e25de8e65a844c1d5/realm%3A%2F%2F127.0.0.1%3A9080%2FInventoryDemo'
2017-03-14 11:29:32.588 Inventory[42484:3812476] Sync: Connection[3]: Resolving '127.0.0.1:9080'
2017-03-14 11:29:32.588 Inventory[42484:3812476] Sync: Connection[3]: Connecting to endpoint '127.0.0.1:9080' (1/1)
2017-03-14 11:29:32.588 Inventory[42484:3812476] Sync: Connection[3]: Connected to endpoint '127.0.0.1:9080' (from '127.0.0.1:59537')
not processed.
2017-03-14 11:29:32.590 Inventory[42484:3812476] Sync: Connection[3]: Session[1]: Sending: BIND(server_path='/InventoryDemo', signed_user_token_size=561, need_file_ident_pair=0)
2017-03-14 11:29:32.590 Inventory[42484:3812476] Sync: Connection[3]: Session[1]: Sending: IDENT(server_file_ident=8879914354123761546, client_file_ident=1, client_file_ident_secret=4116966367839979738, scan_server_version=2, scan_client_version=2, latest_server_version=2, latest_server_session_ident=6697532683229990914)
change notification: SyncPermissionChange {
	id = 090CFAB9-0BD2-464A-864A-CA1A5BE7F23F;
	createdAt = 2017-03-14 18:29:32 +0000;
	updatedAt = 2017-03-14 18:29:32 +0000;
	statusMessage = (null);
	realmUrl = realm://127.0.0.1:9080/InventoryDemo;
	userId = *;
	statusCode = (null);
	mayManage = 0;
	mayRead = 1;
	mayWrite = 1;
}
2017-03-14 11:29:35.763919 Inventory[42484:3812242] [Warning] <_UIPopoverBackgroundVisualEffectView 0x7fed4353c500> is being asked to animate its opacity. This will cause the effect to appear broken until opacity returns to 1.
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

Product Detail View: QoH update on Realm record update

Presently if a products Quantity on Hand (QoH) updates the product detail view's version will not update dynamically; it will update if you go back up to the product list and then re enter the detail view.

This will require object level notification in rerouted to correctly reflect the change as it happens

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.