Technical overview of Shoutem Extensions

This document explains the overall architecture of Shoutem extensions in more details.

What is an extension?

Each Shoutem extension represents a complete, self-contained functionality, e.g. an integration with a third party e-commerce provider. This extension would define multiple screens that will be available in the mobile app, any custom logic for purchasing products, and web pages that allow app owners to configure this integration through the Shoutem Builder.

Extension segments

To accomplish that, we divided extension into two main segments: app, and server. The app segment contains everything that will be bundled within the RN mobile app: this can be anything from simple RN screens to complete native modules implemented using platform specific native technologies. The server segment contains web pages that will be hosted within the Shoutem builder, and allow users to configure and manage extensions while creating their apps.

Besides those two segments, extensions also have an extension.json, file that contains extension metadata. It is used by our server to discover the functionalities that will become available to users in the Shoutem builder after the extension is installed.

Naming

Each extension has a canonical name (full name) that consists of two sections separated by a single dot. The first section represents a developer name, and the second one represents the name of the extension itself. For example shoutem.news. This allows us to avoid collisions between the components provided by different extensions. Each component exported from the extension has a canonical name as well. The canonical name of a component always has an extension canonical name as a prefix, e.g., shoutem.news.ArticleDetailsScreen.

Mobile app segment

The app segment of the extension is an npm package that will be hosted within the Shoutem mobile app. Our mobile app represents an environment in which all extensions are executed, and it provides certain dependencies out of the box. The extension environment is called the platform (@shoutem/platform), and each extension must define a range of platform versions in its extension.json that it’s compatible with. The platform defines certain global dependencies like the version of React, React Native, Redux, Shoutem UI, etc. More information about dependencies provided by the platform can be found in package.json in the platform repository.

Our app architecture is completely modular. At the center of everything is only a simple extension loader that discovers, loads, and initializes all extensions installed in the app. Everything else is implemented through extensions. Even the core Shoutem functionalities (e.g. navigation, push notifications, analytics, …) are implemented as regular extensions no different from any other third party extension.

Some of our extensions are system ones. They’re hidden from our non technical app owners (who don’t have a developer account registered). Only shoutem developer can create system extensions.

Extension lifecycle

When the app is started, we simply load all extensions and run their lifecycle. At that point, shoutem.application system extension navigates to the initial screen of the app, and all other user interaction starts from there.

The primary interface between extension and the rest of the app, extension’s public API, is defined by exports in its app/index.js.


Predefined extension exports

There are several predefined exports that are used by other parts of the system:

  • lifecycle methods - methods that extensions can implement to be notified when the entire app is mounted or unmounted. This can be useful to initialize the extension or clean up when the app is closing. Each of those methods receives an app parameter that represents the current app instance. Each of those methods may also return a promise. If a promise is returned, the next lifecycle method of any extension will not be called until that (any every other) promise is resolved. This is the list of lifecycle methods in order of their invocation:
    • appWillMount - invoked immediately before the mounting of the root app component occurs.
    • appDidMount - invoked after the root app component is mounted and after all promises from a previous lifecycle method are resolved.
    • appDidFinishLaunching - invoked after the app is mounted and after all promises from appDidMount have finished. This is the place to perform any final work before the first screen is rendered.
    • appWillUnmount - invoked immediately before the root app component is unmounted and destroyed. Perform any necessary cleanup in this method.
  • screens - the screens that will be available for navigation. Must have the same name as in extension.json
  • actions - actions that can be attached to shortcuts (see shoutem.auth extension). Must have the same name as in extension.json
  • reducer - the extension reducer that will be mounted under the extension namespace in the state
  • middleware - Redux middleware to register in the Redux store
  • enhancers - Redux enhancers to register in the Redux store


Other exports

Besides the exports mentioned above, in app/index.js you may also export anything else you want to expose to other extensions. All extensions are installed as node modules, so importing from other extensions is done by simply importing from a package with the canonical name of the extension. For example, you can import the getExtensionSettings selector from the shoutem.application extension like this:

import { getExtensionSettings } from 'shoutem.application';
Communication between extensions

The app initializes the Redux store before loading the extensions. Redux is used for state management, and to facilitate easier communication between the extensions. Communication between extensions can be accomplished in a few ways:

  • reading from the state of other extensions
  • dispatching actions from other extensions
  • intercepting actions by using redux middleware
  • directly importing public classes, functions, etc. from other extensions

The preferred way to read the data from the state is to export redux selectors, so that extensions don’t directly depend on the internal state organization of other extensions.


App state

To simplify state management, the entire state of the app is divided into extension namespaces:

{
    'shoutem.application': {
        // state of the "application" extension made by shoutem
    },
    'tom.restaurants': {
        // state of the restaurants extension made by a third party developer tom
    },
    ...
}

Each extension namespace is completely managed by the extension through the (root) reducer exported from its app/index.js.

Modifying extensions

All our extensions are open sourced, so you can modify anything you want by simply forking them. Although this is probably the easiest way to modify extensions, it may not always be the best way to do it. If you fork an extension, you have to make sure to maintain it yourself. In other words, you lose automatic updates and bugfixes implemented by the extension owner.

A better approach may be to perform minimal changes to the extension by creating a new extension. This can be accomplished in several ways:

Read more in Modifying extensions tutorial.


Server segment

Server segment is used for customizing 3 server sides of extensions:

  • Settings pages
  • Data schemas
  • Theme variables

Settings pages that are used to configure the extension through the Shoutem builder. Those pages can be implemented in several ways. They can be a standalone web site implemented using a web framework of your choice. With this approach you have complete freedom to do whatever you want. Pages like that are always be hosted within an iframe in the Shoutem builder. Another way to implement those pages is to use React components. This approach to settings pages is still in development, but it will become a preferred way to implement settings pages. Learn more on how to create settings pages.

Data schemas allow you to define a data type to be stored on the Shoutem backend using a standard JSON Schema format. When a app owner installs your extension in the Shoutem builder, all schemas defined within it and connected with content settings page. become available through web interface. The app owner is able to manually enter data in the Shoutem builder, or import data to the cloud storage from various supported sources. The web interface for data management is created automatically based on the JSON Schema from your extensions. Learn more on how to use Shoutem Cloud

Theme variables schema is used for themes. It also uses the JSON Schema standard to define the theme variables that can be customized by app owners through the Shoutem builder. Using those variables, owners are able to customize themes by changing colors, fonts, sizes, etc. Web interface for customizing theme variables is automatically created in the Shoutem builder based on the theme variables schema when theme from your extension is activated in the app. Learn more about creating theme variables.