Creating a New Manifold App

Introduction

Manifold Apps are written by developers to extend functionality for people's picos. A manifold app consists of two parts; KRL ruleset(s) that provides the functionality for the pico, and a React component that interfaces with that ruleset(s) to provide a better owner experience.

The App KRL

Every app for a pico needs KRL that will do two things; provide the app functionality, and follow the Manifold API in order to be discovered as an app and displayed in Manifold.

The Manifold API requires a "discovery" rule to be in the primary app ruleset:

  rule discovery { 
    select when manifold apps
    send_directive("app discovered...", {"app": app, "rid": meta:rid, "bindings": bindings()} ); 
  }

The discovery rule is used by Manifold to dynamically discover which rulesets constitute a Manifold App. The Manifold SPA will send this event to each pico, and if the ruleset contains this rule, it will be recognized as an app and available for viewing in Manifold.

The developer must define the variable "app" and the function "bindings" in the global block. The app variable looks like this:

app = {"name":"Neighborhood Temperatures","version":"0.0"/* img: , pre: , ..*/};

This is general information relating to the app, such as version number, name, and any other meta information about the app. This variable is not currently used/vital in Manifold, although it may be in the near future.

The bindings function looks like this:

    bindings = function(){
      {
        "temps": get_report()
      };
    }

It is a function that returns an object. This returned object will be passed into the React component as props, to be explained in the next section.

The React Component

Manifold is able to abstract much of the implementation specific details away from app developers so all the developer needs to know is pure React (if the developer wishes to use a helper component library, then whoever is hosting Manifold must provide it). This React component is displayed inside the body of a pico's card in Manifold.

Base Props

There are props passed into a developer's component, and are defined as follows:

  • bindings: the returned object from the binding function provided above. It will be provided on initial rendering of the component, but will not be automatically updated with current information unless the developer specifically handles it. 
  • pico_id: the pico's id
  • eci: the channel/eci to the current pico

Helper Function Props

Each Manifold thing created is really just a pico who is a child of the Manifold owner pico. When a Manifold developer decides to make an app that can be installed in a Manifold thing, we provide a couple of functions that are passed in through props. These functions have had some of the more tedious steps abstracted and serve to help perform actions like raising an event or querying that Manifold thing. Since the action of raising an event or querying is in the context of the Manifold thing, we do not require that the developer provide an eci and we take care of that for them. In this section we will go over these functions and how they can be used.

Helper Functions:

  • signalEvent
    • Description: Used for raising an event on the Manifold thing. The developer only has to worry about providing the parameters mentioned below.
    • The only parameter is a map with the following key-value pairs:
      1. domain (String) The domain of the event.
      2. type (String) the type of the event.
      3. attrs (Map) Any attributes you are passing to the event.

Note: If the parameters for the signalEvent do not make any sense, the following documentation can help Events and Queries Lesson.

How to use:

Let's take a look at how the journal app uses the signalEvent function. The journal app is an app that lets users make entries on daily activities just like a real journal. When a new entry is made, the journal app will record the title and content of the entry (The time the entry is made is handled by the pico engine) and raise an event with the domain of "journal" and type of "new_entry" and passes the title and content as attributes. The event that is raised takes care of storing the new entry.

The journal app uses the signalEvent function in the following manner:

signalEvent example
const promise = this.props.signalEvent({
	domain: "journal",
	type: "new_entry",
	attrs : {
    	title : this.state.title,
    	content : this.state.content,
	}
});


  • manifoldQuery
    • Description: Used for querying the Manifold thing. The developer only has to worry about providing the parameters mentioned below.
    • The only parameter is a map with the following key-value pairs:
      1. rid (String) The ruleset where the function is located.
      2. funcName (String) The name of the function.
      3. funcArgs (Map)(Optional) Any parameters the function requires.

Note: If the parameters for the manifoldQuery do not make any sense, the following documentation can help Events and Queries Lesson.

How to use:

Still using the journal app as an example, lets take a look at how it uses the manifoldQuery function. Like mentioned in the signalEvent "How to use", the journal app allows for journal entries to be made and stored. So in addition, the journal app also allows for the viewing of your entries. So the journal app uses the manifoldQuery function to query the Manifold thing and the "getEntry" function returns the stored entries.

The journal app uses the manifoldQuery function in the following manner:

manifoldQuery example
const promise = this.props.manifoldQuery({
	rid: "io.picolabs.journal",
	funcName: "getEntry"
});

We also provide helpful functions such as customEvent and customQuery for raising an event or querying any pico and not just the Manifold thing but they are are not passed in as props. Instead a Manifold developer can import these functions from the Manifold SDK (explained in the Manifold SDK section). Unlike the signalEvent and manifoldQuery, the customEvent and customQuery functions do require an eci to be passed in.

Manifold SDK

The manifoldSDK.js file contains functions that can be imported by a Manifold developer in order to perform some actions like querying and raising an event in a Manifold app with ease. Our goal was to make these actions easy for the user by abstracting some of the more tedious steps. There are various exported functions in the manifoldSDK.js file but we will cover two important ones in detail.

Useful Exported Functions:

  • customEvent
    • Description: Used for raising an event on any pico. The developer only has to worry about providing the parameters mentioned below.
    • Parameters:
      1. eci (String) The event channel identifier of the pico you wish to raise an event to.
      2. domain (String) The domain of the event.
      3. type (String) The type of the event.
      4. attributes (Map) Any attributes you are passing to the event.
      5. eid (String) The event identifier.

Note: If the parameters for the customEvent do not make any sense, the following documentation can help Events and Queries Lesson.

How to use:

Let's say you have a ruleset in your pico engine that you plan to use for the purpose of receiving notifications about your Manifold things. In this example, you have this ruleset installed in your Manifold owner pico and so if you wish to raise an event that will change the number to contact you will need to have the Manifold owner pico eci. In our example below, the rule has a domain of "notification" and a type of "set_toPhone". The only attribute our rule expects is the "toPhone" attribute and we have set the eid parameter as just "toPhoneSet".

You would call the customEvent function the following way:

customEvent example
const promise = customEvent( getManifoldECI(), "notification", "set_toPhone", {"toPhone": this.state.toPhone }, 'toPhoneSet');


  • customQuery
    • Description: Used for querying any pico. The developer only has to worry about providing the parameters mentioned below.
    • Parameters:
      1. eci (String) The event channel identifier of the pico you wish to query.
      2. ruleset (String) The ruleset where the function is located.
      3. funcName (String) The name of the function.
      4. params (Map) Any parameters the function requires.

Note: If the parameters for the customQuery do not make any sense, the following documentation can help Events and Queries Lesson.

How to use:

Following the same scenario from the customEvent, let's say you wish query the Manifold owner pico for the current number you have listed to contact. In this example, the ruleset is called "manifold_notifications" and the function name is "getToPhone". There are also no parameters the function requires.

You would call the customQuery function the following way:

customQuery example
const promise = customQuery( getManifoldECI(), "manifold_notifications", "getToPhone", {});

There are multiple functions in the Manifold SDK and you may never find yourself using them all. In this section we covered only two useful functions that can be imported. In the next section we will talk about another useful function that can be imported from the Manifold SDK which is the displayError function and it is useful for displaying any errors that may occur from API calls.

Error Handling

Manifold offers an easy way to display API error messages and status code via a modal. This modal can be displayed over any page of Manifold including over any apps.

How it works:

When a Manifold user is logged in, the React app will display the Full component which essentially holds the React Router that redirects the user to the correct pages on the website. The ErrorModal component is included in the render function of the Full component but does not take any room since its has been told to stay hidden. The ErrorModal has its state kept by Redux so that it can be toggled by any component. The ErrorModal is placed in the Full component since it sits at the top of the app's view hierarchy and this lets us display wherever we like. There is an ErrorModal also placed in the LoginBody component in order to catch any login authentication issues because the LoginBody does not sit inside the Full component. In order for the ErrorModal to be displayed it needs to be toggled by calling the Redux action we the developers at Pico Labs have written called "toggleErrorModal" and the state change will propagate to the ErrorModal. We know that importing the action to every single component and calling the "connect()" Redux function so that we can connect the component with the Redux store is a lot, so, we made it easier by having the manifoldSDK.js take care of it. So all a Manifold developer has to do is import the "displayError" function that takes care of it from the manifoldSDK.js.

Here is the definition of the "displayError" action:

displayError Action definition
/*
	value (Boolean) true must be passed in to toggle the modal.
	message (String) the error message you want users to see.
	status (String or Number) the status code of the error response.
*/
export function displayError(value, message, status) {
  store.dispatch(toggleErrorModal(value, message, status))
}


How to use:

  1. Import the "displayError" function from the manifoldSDK located at src/utils/manifoldSDK.js
  2. Wherever you are making an API call, be sure you are catching the resp whether it is bad or good response. (Example provided)

    Catching API Response Example
    getProfileInfo() {
    	const profileGetPromise = retrieveOwnerProfile();
        profileGetPromise.then(
          (resp) => {
            // Handle good response.
          },
          (error) => {
            // Handle bad response.
          })
    }
  3. Call the "displayError" function that you imported and pass in values for toggling the modal (Boolean), the error message, and the status code. (Example of what it should look like provided)

    Example of calling the displayError function
    getProfileInfo() {
    	const profileGetPromise = retrieveOwnerProfile();
        profileGetPromise.then(
          (resp) => {
            // Handle good response.
          },
          (error) => {
              displayError(true, "Error getting manifold profile.", "404")
          })
    }

How it looks:

Note: The modal will display multiple errors if the "displayError" function is called more than once.

Other Features:

  1. The "Report?" button is located on the bottom left of the ErrorModal so that Manifold users can email us of any issues. A Manifold developer can easily change the email destination to be themselves by simply changing the "href" property to the button.
  2. The "Copy" button is located on the bottom right of the ErrorModal so that Manifold users can copy all the errors in the ErrorModal to their clipboard. A Manifold user can then email us using the "Report?" button and paste the error in the body of their email so that we can have an easier time debugging any issues they may be experiencing. A Manifold Developer can use this button for similar purposes.












Copyright Picolabs | Licensed under Creative Commons.