Skip to main content

App Navigation Generator

To generate app navigations, you need to follow some steps given below.

Requirements

  • Rollup (rollup.config.mjs).
  • Admin Layout Pacakge (@admin-layout/gluestack-ui-mobile).

For LifeCycle Component and Interaction Manager functionality,use key in compute.ts as below

  • withLifeCycle
    (Use this when only lifecylc component is needed as wrapper for component)
  • withInteraction
    (Use this when only interaction manager functionality is needed , applied only on android)
  • withLifeCycleInteraction
    (Use this when both lifecylc component and interaction manager functionality is required)

Note: Default duration is 200 ms

{
withLifeCycle: true;
}
{
withInteraction: true;
}
{
withLifeCycleInteraction: true;
}

or with custom duration (Duration is not required for withLifeCycle)

{
withInteraction: {
duration: 100;
}
}
{
withLifeCycleInteraction: {
duration: 100;
}
}

For Icons we use @expo/vector-icons,use icon with name and props in compute.ts as below ,used as Drawer icons or BottomTab icons

import {MaterialCommunityIcons} from '@expo/vector-icons';
const MyComponent = () => {
return <MaterialCommunityIcons name="home" size={24} color="black" />;
}
//Similar
icon: {
// Icon Type from @expo/vector-icons directory
name: 'MaterialCommunityIcons',
// props of icon
props: {
name: 'home',
size: 24,
color:'#eee',
},
}

importPaths in compute.ts file is used for converting import to absolute path as below and it is handled by @common-stack/rollup-vite-utils

 {
importPaths: {
login: () => import('./screens/Login'),
},
// converted to
"importPaths": {
"login": '@yourpkg/pkgname/lib/screens/Login.js',
},
}

unauthenticatedComponent and customHeader are the only keys in compute.ts file is using importsPath as below and it is handled by @admin-layout/gluestack-ui-mobile

unauthenticatedComponent:'$.importPaths.homeGuestComponent',
customHeader:{
component:'$.importPaths.navigationHeader',
},

Step 1:

  • Create compute.ts file inside module package/src folder
  • Update module package tsconfig file to target ES6 module
  • Create rollup.config.mjs in module package root directory
  • Update rollup.config.mjs input to src/index.ts( if index.jsx file then replace it to index.ts) and extend module package rollup config with base rollup.config.base.mjs createRollupConfig function
  • Update module package package.json add "type": "module" and replace scripts "build:lib":"tsc" with "build:lib": "rollup -c rollup.config.mjs"

mypackage/src/compute.ts

/* eslint-disable import/no-extraneous-dependencies */
import { getFilteredRoutes } from '@common-stack/client-react/lib/utils/filteredRoutes.js';
import { IRouteData } from '@common-stack/client-react/lib/interfaces/router.js';
import { IPageStore } from '@adminide-stack/core/lib/interfaces/page-store.js';

export const myPageStore: IPageStore[] = [
{
key: 'home',
path: '/:orgName/l/home',
name: 'Home',
exact: false,
menu_position: 'bottom',
auth: true,
component: () => import('./screens/Home'),
withLifeCycleInteraction: {
duration: 100,
},
icon: {
name: 'MaterialCommunityIcons',
props: {
name: 'home-variant-outline',
size: 24,
color: '#eee',
},
},
importPaths: {
navigationHeader: () => import('./components/NavigationHeader/NavigationHeader'),
// homeGuestComponent: () => import('./screens/Login'),
},
// unauthenticatedComponent:'$.importPaths.homeGuestComponent',
customHeader: {
name: 'NavigationHeader',
component: '$.importPaths.navigationHeader',
props: {
showTitle: false,
showOrganizationTitle: true,
showToggle: true,
showFilter: true,
isSearchBack: false,
},
},
props: {
initialParams: { orgName: null },
options: {
headerShown: true,
title: 'Home',
headerTitle: 'Home',
priority: 1,
tabBarActiveTintColor: '#000',
tabBarInactiveTintColor: 'black',
tabBarLabel: 'Home',
},
},
},
];

mypackage/rollup.config.mjs

import { createRollupConfig } from '../../../rollup.config.base.mjs';
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';

// Define any additional plugins specific to this bundle
const additionalPlugins = [
json(),
resolve(), // resolves node_modules
// terser(), // minifies the output
];

// Use the createRollupConfig function to merge the base and specific configurations
export default [
createRollupConfig({
input: ['src/index.ts'],
output: [
{
dir: 'lib',
format: 'es',
name: 'Accounts',
compact: true,
exports: 'named',
sourcemap: true,
preserveModules: true,
chunkFileNames: '[name]-[hash].[format].js',
globals: { react: 'React' },
},
],
plugins: [
// Spread in additional plugins specific to this config
...additionalPlugins,
],
// Enable caching for faster rebuilds
cache: true,
}),
];

mypackage/package.json

{
...
"type": "module",
"scripts": {
...
"build:lib": "rollup -c rollup.config.mjs",
...
}
}

Step 2:

  • Create config.json file inside portable-devices/mobile root folder if not exists and add/update mobileConfig and modules key to update modules and app generation required mobile config.
  • Create generate-navigations.mjs or yourfilename.mjs inside portable-devices/mobile root folder.
  • Update portable-devices/mobile package.json scripts and add script to generate development and production app navigation files with above mjs files.
  • Update portable-devices/mobile/src/modules/modules.tsx file , import modules and useGetModules hook from autogenerated src/app/modules.ts file and app navigation from autogenerated src/app/navigation.tsx file to use with src/modules/modules.tsx file for autogenerated navigation and import and add Navigation from src/modules/modules.tsx in src/App.tsx file.

portable-devices/mobile/config.json

{
"commonPaths": {},
"copyOperations": [],
"mobileConfig": {
"initialRouteName": "",
"unauthenticatedComponentPath": "",
"customTabBarPath": "",
"customDrawerPath": "",
"customHeaderPath": ""
},
"modules": ["@somepkg/yourpkg1", "@admin-layout/gluestack-ui-mobile"],
"paths": {}
}

Note: here mobileConfig initialRouteName is used when we want to change the app initial navigation redirect route,unauthenticatedComponentPath is the global unauthenticated component absolute path like '@yourpkg/somemobilepkg/lib/screens/Login.js' , same for other customTabBarPath,customDrawerPath or customHeaderPath.

portable-devices/mobile/generate-navigations.mjs

import path from 'path';
import { fileURLToPath } from 'url';
import {
GenerateMobileNavigations,
readJsonFile,
} from '@admin-layout/gluestack-ui-mobile/lib/utils/generateMobileNavigations.js';
if (process.env.NODE_ENV === 'development') {
import('dotenv').then((dotenv) => {
dotenv.config({ path: `${process.cwd()}/${process.env.ENV_FILE}` });
});
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// App navigations setting config file path
const configFilePath = path.join(__dirname, 'config.json');
// Default app navigations directory path
const appDirPath = path.join(__dirname, './src/app');

const generateNavigations = async () => {
const config = await readJsonFile(configFilePath);
//App navigations generator class only appDirPath and modules are required
const app = new GenerateMobileNavigations({
appDirPath: appDirPath,
modules: config.modules,
initialRouteName: config.mobileConfig.initialRouteName, // (Optional)
unauthenticatedComponentPath: config.mobileConfig.unauthenticatedComponentPath, // (Optional)
customTabBarPath: config.mobileConfig.customTabBarPath, // (Optional)
customDrawerPath: config.mobileConfig.customDrawerPath, // (Optional)
customHeaderPath: config.mobileConfig.customHeaderPath, // (Optional)
});
try {
await app.generateAppNavigations();
console.log('app navigation generated');
} catch (error) {
console.error('Error generating app navigation:', error);
}
};

generateNavigations();

Note: generate-navigations.mjs will generate app navigations inside src/app directory

portable-devices/mobile/package.json

{
...
"scripts": {
...
"eas-build-post-install": "lerna exec --scope='{@messenger-box/core,...}' yarn build && yarn generate-app-navigations-prod",
"watch": "yarn generate-app-navigations && cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env expo start --clear --localhost",
"generate-app-navigations": "yarn cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env node generate-navigations.mjs",
"generate-app-navigations-prod": "node generate-navigations.mjs",
...
}
}

portable-devices/mobile/src/modules/modules.tsx

import * as React from 'react';
import { enableScreens } from 'react-native-screens';
import {
ErrorBoundary,
NavigationContainerComponent,
ApplicationErrorHandler,
Box,
Spinner,
} from '@admin-layout/gluestack-ui-mobile';
import { InversifyProvider, PluginArea } from '@common-stack/client-react';
import appModules, { useGetModules } from '../app/modules';
import AppNavigations from '../app/navigation';

enableScreens(true);

export const MainRoute = ({ container }: any) => {
const [mainFeatures, setMainFeatures] = React.useState<any>(null);
const { appRouteConfig } = useGetModules();

React.useEffect(() => {
if (appRouteConfig) {
appModules.routeConfig = appRouteConfig;
setMainFeatures(appModules);
}
}, [appRouteConfig]);

const loadingComponent = () => <Box flex={1}>{<Spinner />}</Box>;

if (!mainFeatures || mainFeatures?.routeConfig?.length == 0) return loadingComponent();

const plugins = mainFeatures?.getComponentFillPlugins();
const configuredRoutes = mainFeatures?.getConfiguredRoutes2('/');

return (
<InversifyProvider container={container} modules={mainFeatures}>
{mainFeatures?.getWrappedRoot(
<ErrorBoundary>
<NavigationContainerComponent configurableRoutes={configuredRoutes} independent={true}>
<AppNavigations />
<PluginArea />
<ApplicationErrorHandler plugins={plugins} />
</NavigationContainerComponent>
</ErrorBoundary>,
)}
</InversifyProvider>
);
};

export default appModules;

portable-devices/mobile/src/App.tsx

import 'reflect-metadata';
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { ApolloProvider } from '@apollo/client/index.js';
import { Provider } from 'react-redux';
import { GluestackUIProvider } from '@admin-layout/gluestack-ui-mobile';
import { config } from './config/gluestack-ui.config';
import { SlotFillProvider } from '@common-stack/components-pro';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import { createReduxStore, history } from './config/redux-config';
import { createClientContainer } from './config/client.service';
import { LogBox, SafeAreaView } from 'react-native';
import { MainRoute } from './modules/modules';

const { apolloClient: client, container, serviceFunc } = createClientContainer();
const { store } = createReduxStore(history, client, serviceFunc(), container);

export default function () {
let persistor = persistStore(store as any);
return (
<SlotFillProvider>
<ApolloProvider client={client}>
<Provider store={store}>
<PersistGate persistor={persistor}>
<SafeAreaView style={{ flex: 1 }}>
<GluestackUIProvider config={config} colorMode="light">
<MainRoute container={container} />
</GluestackUIProvider>
</SafeAreaView>
</PersistGate>
</Provider>
</ApolloProvider>
</SlotFillProvider>
);
}

For testing or adding route config in portable-devices/mobile create compute.ts file inside portable-devices/mobile/src/compute.ts and add routeConfig as above from step 1. This will generate route config json in src/app/main_routes.json and include your routes in generated navigations.