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"
/* 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',
},
},
},
];
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,
}),
];
{
...
"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.