Inside QuickBrick

Modularity

The QuickBrick code base is made of several packages which interact with one another. This allows for a very modular setup. These packages can be used together, but also, to some extend, independently from one another. All these packages are scoped to the applicaster domain, but they are publicly accessible1.

The main packages are :

  • @applicaster/zapp-react-native-app: the core App component
  • @applicaster/zapp-react-native-bridge: provides modules wrapping the native features of the Zapp app
  • @applicaster/zapp-react-native-redux: provides the logic for state management inside the app
  • @applicaster/zapp-react-native-ui-components: UI components to compose the app - the components exposed directly in the package match the UI Builder components, but this package may include finer components for more reusability
  • @applicaster/zapp-react-native-utils: utilities to interact with primitive types, plugins, etc...
  • @applicaster/zapplicaster-cli: CLI tool to manage the QuickBrick app development workflow

On top of these, there is a @applicaster/zapp-react-native-development-app package which only represents the entry point of the react native App. This package's content is generated by the zapplicaster-cli tool which injects the app's configuration into a project template to create a contextualised React Native entry point, when working on the main repo. This can be created manually when working on an plugin which would not sit in this repo. QuickBrick was designed to provide a consistent development experience, wether one is developing on the core features of the repo, or on a specific plugin.

Dependency injection

QuickBrick uses a dependency injection pattern to insert the desired pieces of logic that constitute the app. There are very few static features which cannot be overriden by injecting custom dependencies in the app's entry point. Typically, the @applicaster/zapp-react-native-app packages gives you a createZappApp function, which takes a configuration object to provide the pieces the app needs : components, rivers configuration, styles, plugin modules & their configuration, etc. All you need to do from this point is to create an app using the generated ZappApp component, inject your custom props, then start the react app with the provided function.

Your typical entry point file will look like this:

import React from "react";
import { AppRegistry } from "react-native";

// import your plugins, custom ui components & configuration files
// the CLI tool will generate this automatically from the Zapp configuration of your app version
import pluginX from "plugin-x";
import pluginY from "plugin-y";
import customComponentA from "custom-component-a";
import customComponentB from "custom-component-b";

import {
  rivers,
  styles,
  pluginConfigurations,
  remoteConfigurations
} from "./config";

// import the QuickBrick App package
import {
  createZappApp,
  startZappApp
} from "@applicaster/zapp-react-native-app";

// expose your plugins - this is also automatically generated from your plugins' manifest when using the CLI tool
const plugins = [
  {
    module: pluginX,
    name: "pluginX",
    type: "general"
  },
  {
    module: pluginY,
    name: "pluginY",
    type: "ui_component"
  }
];

// expose your components - they will be merged with the default built-in components
const components = {
  customComponentA,
  customComponentB
};

// expose your custom reducers & middlewares
const reduxStoreOptions = {
  additionalReducers,
  additionalMiddlewares
};

// create your ZappApp component
const ZappApp = createZappApp({
  components,
  rivers,
  styles,
  pluginConfigurations,
  remoteConfigurations,
  plugins,
  reduxStoreOptions,
  appSettings: {}
});

// create your App - here props will come from the native app
const App = props => <ZappApp {...props} />;

// start your app !
startZappApp(AppRegistry, App);

App Logic

From there on, the ZappApp component will start to render the app based on the provided data. There are 4 key principles :

Screens:

The app will try to compose screens, which are defined from the rivers.json configuration. For each screen, the app will either render a plugin, or a River which corresponds to the UI Builder's general content screen. A River will then render all ui_components it contains, and pass it the data associated with that component in the rivers.json Each screen as data attached to it, including navigation data, but also in most cases a data source information which can be resolved to actual data through the Zapp-Pipes engine.

Layout:

The app will compose the screen inside a layout. There is currently only a layout for iOS and one for android, but this could be extended in the future. This layout includes a navbar and a menu element, which can be customized by plugins, and source their data from the navigation properties of the rivers.json.

Routes:

The app's navigation is managed by a recursive route pattern of the shape /:screen_type/:screen_id. When you tap on a navigation element on the screen, the app will try to resolve the expected content to render, and an identifier. Arbitrary data can also be passed when navigating to a route, which allows each screen and component to be provided with the full expected context to be rendered. For instance, when tapping on an item in a list on the home screen:

  • if this item points to another feed, it will generate a route like /river/<home-river-id>/river/<target-river-id>
  • if this item points to an article, it will generate a route like /river/<home-river-id>/article/<target-article-id> In the second scenario, the navigation layer will try to resolve a plugin capable of consuming content of the article type, and compose a screen with this plugin, in the relevant platform layout, and inject into it the data retrieved for this article

Plugin decoration:

When injecting a custom plugin or component in the App, a few features are automatically injected through a Higher Order Component pattern. Each component gets automatically:

  • a navigator object to replace or push routes (allowing for custom data injection), or goBack, and a routeData function to retrieve the current's route specific data
  • a zapp pipes data connector which will automatically attempt to load data from the data source specified in this component, if any

On top of this, all plugins are wrapped in a HoC which passes props as they are expected by a legacy Zapp React Native plugins which relies on the current native adapters for iOS & Android.

Check out the API documentation to know more about the specific packages' features.

[1]: Authentication to our npm account is still required as this stage, since the app relies on Zapp-Pipes packages which access is still restricted.

results matching ""

    No results matching ""