What we’ll build

- Setting up users and participants in the Adalo editor.
- Seeing past conversations.
- Sending messages directly.
- Moving between inbox and chat view.
Prerequisites
- A free Adalo account with developer mode on.
- Your preferred IDE (I use Visual Studio Code).
- Node.js version
>=10.2.
- A TalkJS account (free and unlimited during development).
- You can find your TalkJS appID by signing into your TalkJS account and navigating to the settings tab in your dashboard, where the APP ID is listed.

1. Create a new Adalo library
create-adalo-component command in your terminal. This command sets up the environment for custom Adalo components, installs dependencies, adds scripts and provides a basic starter manifest.json and component. Make sure to replace chat-component with your desired library name.npx create-adalo-component chat-component
chat-component with whatever you named your folder.cd chat-component
package.json to include peerDependencies for React and React Native, and update devDependencies with the latest Adalo CLI version, as illustrated here:"peerDependencies": {
"react": "18.2.0",
"react-native": "0.72.5"
},
"devDependencies": {
"@adalo/cli": "^0.0.59"
}npm install to have all dependencies installed
In the next section, we’ll configure the manifest.json.2. Configure the manifest.json

Prop name | Description | Type |
talkJsApplicationID | The TalkJS application ID. | text |
userId | The ID of the user joining the conversation. | text |
name | Display the name of the participant. | text |
email | Email address of the user. | text |
photo | User's photo. | image |
chatView | Toggle to enable participants. | boolean |
pUserId | Participant's user ID. Only enabled when chatView is true. | text |
pName | Participant's name. Only enabled when chatView is true. | text |
pPhoto | Participant's photo. Only enabled when chatView is true. | text |
pEmail | Participant's email. Only enabled when chatView is true. | text |
{
"displayName": "TalkJS Chat demo",
"defaultWidth": 500,
"defaultHeight": 600,
"resizeY": true,
"resizeX": true,
"components": "./index.js",
"icon": "./ChatThumbnail.png",
"props": [
{
"name": "talkJsApplicationID",
"displayName": "appID",
"type": "text",
"helpText": "The talkJS app ID"
},
{
"name": "userId",
"displayName": "ID",
"type": "text",
"helpText": "The id of the user joining this conversation"
},
{
"name": "name",
"displayName": "Name",
"type": "text",
"helpText": "The display name for the participant in the conversation"
},
{
"name": "photo",
"displayName": "Photo",
"type": "image",
"helpText": "The optional user image for the call"
},
{
"name": "email",
"displayName": "Email",
"type": "text",
"helpText": "The user's email"
},
{
"name": "chatView",
"displayName": "Start in chat",
"type": "boolean",
"helpText": "Starts in chat view; add a participant if enabled"
},
{
"name": "pUserId",
"displayName": "ID",
"type": "text",
"helpText": "The id of the user joining this conversation",
"enabled": {
"chatView": true
}
},
{
"name": "pName",
"displayName": "Name",
"type": "text",
"helpText": "The display name for the participant in the conversation",
"enabled": {
"chatView": true
}
},
{
"name": "pPhoto",
"displayName": "Photo",
"type": "image",
"helpText": "The optional user image for the call",
"enabled": {
"chatView": true
}
},
{
"name": "pEmail",
"displayName": "Email",
"type": "text",
"helpText": "The user's email",
"enabled": {
"chatView": true
}
}
]
}3. The component’s entry point
create adalo component, Adalo gave us an index.js template. The index.js is where we define our component's code and logic. editorImage from the demo repository to your src folder, ensuring the file name and path match your project's structure. Later, we'll use this editorImage whenever the user is in Adalo's editor.import React, { useState, useEffect } from "react";
import { View, Image, StyleSheet, ActivityIndicator } from "react-native";
import editorImage from "./EditorImage.png";const ChatView = (props) => {
const {
editor,
talkJsApplicationID,
userId,
name,
email,
photo,
pName,
pUserId,
pEmail,
pPhoto,
_height,
chatView
} = props;useState hook: me and other. These states will be used to store information about the users involved in the conversation.const [me, setMe] = useState(null);
const [other, setOther] = useState(null);userID and a name. Additionally, for photos, TalkJS expects a URL. In Adalo, the URL of an image can be found in the image.uri property of the image object. Since our images are photo and pPhoto, we'll use photo.uri and pPhoto.uri respectively.
useEffect hooks update the me and other states and set their values, including the photo URL, when available.useEffect(() => {
if (userId && name) {
setMe({
id: userId,
name: name,
email: email,
photoUrl: photo?.uri,
});
}
}, [userId, name, email]);
useEffect(() => {
if (pUserId && pName) {
setOther({
id: pUserId,
name: pName,
email: pEmail,
photoUrl: pPhoto?.uri,
});
}
}, [pUserId, pName, pEmail, pPhoto]);return section of the component is responsible for rendering different UI elements based on certain conditions:- If the
editorprop istrue, it displays an editor image.
- If the
mestate is not set or thechatViewis enabled and theotherstate is not set, it displays an activity indicator.
- Otherwise, it would render a
ConversationUIcomponent with the appropriate user information. However, for now, we'll have it returnnull, and we'll expand on this in the next section.
return (
<>
{editor ? (
<Image
source={editorImage}
style={{
flex: 1,
height: _height,
justifyContent: "center",
alignItems: "center",
}}
/>
) : !me || (chatView && !other) ? (
<View style={{ height: _height, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#242526" />
</View>
) : null}
</>
);
export default ChatView;4. The web version of the chat component
ConversationUI folder for mobile and web versions. Add index.web.js for the web component, which will display the inbox and support one-on-one chats.import React, { useCallback } from "react";
import { View, ActivityIndicator } from "react-native";
import Talk from "talkjs";
import { Session, Inbox } from "@talkjs/react";npm install talkjs @talkjs/reactconst ConversationUI = ({
me,
other,
ID,
chatView,
_height,
}) => {
// ...
}useCallback hook to create a function called syncUser that synchronizes the local user with TalkJS.const syncUser = useCallback(() => new Talk.User(me), []);syncConversation function synchronizes the conversation between the local user and the other participant. It creates a conversation, sets participants, and returns the conversation object.const syncConversation = useCallback(
(session) => {
const otherUser = new Talk.User(other);
const conversation = session.getOrCreateConversation(Talk.oneOnOneId(me.id, other.id));
conversation.setParticipant(session.me);
conversation.setParticipant(otherUser);
return conversation;
},
[]
);Inbox component, which includes styling, loading indicators, and optionally, the syncConversation function based on the chatView flag.const inboxProps = {
style: { width: "100%", height: _height },
className: "chat-container",
loadingComponent: (
<View style={{ height: _height, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#242526" />
</View>
),
...(chatView && { syncConversation }),
};Session. We provide the appId and syncUser function, and pass the inboxProps to the Inbox.return (
<View>
<Session appId={ID} syncUser={syncUser}>
<Inbox {...inboxProps} />
</Session>
</View>
);export default ConversationUI;5. The mobile version of the chat component
index.js` file in the /ConversationUI folder,
chatView true displays the Chatbox, while false shows the ConversationList.Install dependencies
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator } from 'react-native';
import * as TalkRn from '@talkjs/react-native';npm install --save @talkjs/react-native @notifee/react-native @react-native-community/push-notification-ios @react-native-firebase/app @react-native-firebase/messaging react-native-webviewIncorporate a header for mobile consistency
InboxHeader.js in the ConversationUI folder and insert the code below:import React from "react";
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
import ChevronLeft from "./icons/ChevronLeft";
const Header = ({ onBackPress}) => {
return (
<TouchableOpacity
onPress={onBackPress}
style={[styles.header]}
>
<View style={styles.backButton}>
<ChevronLeft width="16" height="16" color="gray" />
<Text style={[styles.title]}>Inbox</Text>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
header: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 16,
paddingHorizontal: 16,
marginBottom: 8,
borderRadius: 8,
borderWidth: 1,
borderColor: "#ddd",
backgroundColor: "#fff",
},
backButton: {
flexDirection: "row",
alignItems: "center",
marginRight: 8,
},
title: {
fontSize: 14,
color: "black",
},
});
export default Header;import React from 'react';
import Svg, {Path} from 'react-native-svg';
const ChevronLeft = ({
width = 16,
height = 16,
color = 'currentColor',
strokeWidth = 1,
}) => {
return (
<Svg width={width} height={height} viewBox="0 0 16 16" fill="none">
<Path
fill-rule="evenodd"
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0"
fill={color}
stroke={color}
strokeWidth={strokeWidth}
/>
</Svg>
);
};
export default ChevronLeft;Header component into your index.js file located in the ConversationUI directory.import Header from './InboxHeader';Build the ConversationUI component
ConversationUI component that receives props like me, other, ID, _height, and chatView:const ConversationUI = ({
me,
other,
ID,
_height,
chatView,
}) => {const [conversationBuilder, setConversationBuilder] = useState(null);
const [showConversationList, setShowConversationList] = useState(null);useEffect hook to handle the initialization based on the chatView prop:useEffect(() => {
if (!chatView) {
setShowConversationList(true);
} else {
const builder = TalkRn.getConversationBuilder(TalkRn.oneOnOneId(me.id, other.id));
builder.setParticipant(me);
builder.setParticipant(other);
setConversationBuilder(builder);
}
}, [me, other]);onSelectConversation function to handle conversation selection and create an onBackPress function to go back to the conversation list:const onSelectConversation = event => {
setConversationBuilder(event.conversation);
};
const onBackPress = () => {
setShowConversationList(true);
};TalkRn.Session and export the ConversationUI component as the default export.return (
<>
<TalkRn.Session appId={ID} me={me}>
<View style={{ height: _height }}>
{showConversationList ? (
// Display the ConversationList when showConversationList is true.
<TalkRn.ConversationList
onSelectConversation={onSelectConversation}
loadingComponent={
<View style={{ height: _height, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#242526" />
</View>
}
/>
) : (
// Display the Chatbox when showConversationList is false and conversationBuilder is available.
conversationBuilder && (
<>
<Header onBackPress={onBackPress} />
<TalkRn.Chatbox
conversationBuilder={conversationBuilder}
loadingComponent={
<View style={{ height: _height, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#242526" />
</View>
}
/>
</>
)
)}
</View>
</TalkRn.Session>
</>
);
};
export default ConversationUI;6. Finish up the main component
import ConversationUI from "./ConversationUI";return (
<>
{editor ? (
<Image
source={editorImage}
style={{
flex: 1,
height: _height,
justifyContent: "center",
alignItems: "center",
backgroundColor: "red",
}}
/>
) : !me || (chatView && !other) ? (
<View style={{ height: _height, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#242526" />
</View>
) : (
<ConversationUI
me={me}
other={other}
ID={talkJsApplicationID}
_height={_height}
chatView={chatView}
/>
)}
</>
);
};7. Add install scripts
- Update the
compileSdkVersionin the build.gradle file, following TalkJS's troubleshooting tips.
- Adjust the android.work version to align with Notifee and Adalo's requirements.
- Incorporate audio permissions for voice chat functionality.
Android install script
scripts folder, then add an install_android.sh file inside it. You'll need to script changes to the build.gradle file and configure permissions, drawing inspiration from the example script below.#!/bin/bash
set -e
set -x
rootGradleFile="android/build.gradle"
appGradleFile="android/app/build.gradle"
nl=$'\n'
androidManifestFile="android/app/src/main/AndroidManifest.xml"
# Function to update compileSdkVersion
update_compileSdkVersion() {
sed -i "s/compileSdkVersion = [0-9]*/compileSdkVersion = 34/" "$rootGradleFile"
}
# Function to add resolutionStrategy in the app build.gradle
add_appResolutionStrategy() {
resolutionStrategyLine="configurations.all {\\ \\${nl} resolutionStrategy.force 'androidx.work:work-runtime:2.7.0'\\${nl}}\\${nl}"
# Check if the line already exists
if ! grep -q "resolutionStrategy.force 'androidx.work:work-runtime:2.7.0'" "$appGradleFile"; then
# Insert the resolutionStrategy line after dependencies block
sed -i "/android {/a \\
$resolutionStrategyLine" "$appGradleFile"
fi
}
# Function to add permissions in AndroidManifest.xml
add_permissions() {
# Check if permissions already exist
if ! grep -q "android.permission.MODIFY_AUDIO_SETTINGS" "$androidManifestFile"; then
echo "Adding permissions to AndroidManifest.xml"
sed -i "/android.permission.INTERNET/a\\
<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>\\
<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>" "$androidManifestFile"
else
echo "Permissions already exist in AndroidManifest.xml"
fi
}
# Update compileSdkVersion
update_compileSdkVersion
# Add resolutionStrategy in the app build.gradle
add_appResolutionStrategy
# Add permissions in AndroidManifest.xml
add_permissions
echo "compileSdkVersion updated in $rootGradleFile"
echo "compileSdkVersion updated in $gradleFile"
echo "Permissions updated in $androidManifestFile"iOS install script
install_ios.sh file to manage Podfile updates and enable microphone access. Consider this sample script as a reference.#!/bin/bash
set -e
set -x
name=$PROJECT_NAME
podfile="ios/Podfile"
target=$name
infoPlist="ios/$name/Info.plist"
# Function to add a pod entry within the target block if it does not exist
add_pod_to_target() {
if ! grep -q "$1" "$podfile"; then
echo "Adding $1 to target $target in Podfile"
# Use awk to insert the pod lines within the target block
awk -v podline="$1" -v target="$target" '
$0 ~ "target \x27" target "\x27 do" {print; inBlock=1; next}
inBlock && /end/ {print podline; print; inBlock=0; next}
{print}
' "$podfile" > tmpfile && mv tmpfile "$podfile"
else
echo "$1 already in target $target in Podfile"
fi
}
# Enable modular headers for all pods
echo "Enabling modular headers globally in Podfile"
if ! grep -q "use_modular_headers!" "$podfile"; then
sed -i '' '1s/^/use_modular_headers!\n/' "$podfile"
fi
# Modify the podfile within the target
add_pod_to_target " pod 'Firebase', :modular_headers => true"
add_pod_to_target " pod 'FirebaseCore', :modular_headers => true"
add_pod_to_target " pod 'GoogleUtilities', :modular_headers => true"
# Ensure RNFirebaseAsStaticFramework setting is added within the target block
add_pod_to_target "\$RNFirebaseAsStaticFramework = true"
# Check and add microphone usage description in Info.plist
if grep -q "<key>NSMicrophoneUsageDescription</key>" "$infoPlist"; then
echo "Microphone already supported in $infoPlist, nothing to do here."
else
echo "Adding NSMicrophoneUsageDescription to $infoPlist"
plutil -insert NSMicrophoneUsageDescription -string 'Chat needs microphone to record messages' "$infoPlist"
fi
echo "Podfile configured for target $target"
echo "Info.plist configured for microphone support"
Make the scripts executable and adding to the Adalo.json
chmod +x ./scripts/install_ios.sh
chmod +x ./scripts/install_android.shadalo.json under iosInstallScript and androidInstallScript.{
"displayName": "Chat view",
"components": [
{
"name": "TalkJs",
"manifest": "./src/components/TalkJs/manifest.json"
}
],
"author": "Demo user",
"description": "Add professional chat to your Adalo apps",
"logo": "./example-logo.png",
"requiresThirdPartySubscription": true,
"iosInstallScript": "./scripts/install_ios.sh",
"androidInstallScript": "./scripts/install_android.sh"
}8. Test and publish the component
Test the component on the web
npx adalo dev. This command will list the component under the 'Development' section in any Adalo app you choose for testing.

Test the component in a React Native environment
Publish the component
npx adalo publish in your terminal. Currently, your Node version must be ≥ 10.2 and ≤ 16.15 for the command to function correctly.
If you encounter an error while publishing, changing your Node version with nvm use <node version> could fix the problem.
Conclusion
- Integrating group chat capabilities
- Implementing notifications
- Introducing additional views, such as a popup widget
.png?table=block&id=9ba33ac6-8e12-48f6-b980-4333b612ec56&cache=v2)