Data components
Data components are responsible for managing data between the UI Components and the persistence layer. They also interact with the PubNub service by sending and receiving messages and signals.
Available components include:
Overview
PubNub Chat Components for Android utilize several layers of configurable data to drive the functionality found in the UI Components. The goal of the data layer is to provide a simple and robust foundation that can be used across any chat implementation. The two main forms of data used by the components are persistent and network objects.
Persistent data
All the data is stored as entities in a local database using Room, an abstraction layer over SQLite. Room provides the following benefits:
- Compile-time verification of SQL queries.
- Convenience annotations that minimize repetitive and error-prone boilerplate code.
- Streamlined database migration paths.
You can find the default implementation of the database in ChatProvider. It contains data entities, data access objects, and database implementation. Additionally, ChatProvider uses the repository design pattern that allows for separation of concerns by abstracting the data persistence logic.
To learn more about Room, refer to the official Android docs.
Network data
Network objects are most commonly used when interacting with PubNub APIs. Those objects exist only at the network layer and need to be converted with Data Mappers to be used in the application.
UI data
The UI Components don't use persistent or network data but extra UI data objects are created for them instead. These UI data objects contain only user-readable information. For example, to display a timetoken, it must be converted to a date string first.
Data Mappers
The role of Data Mappers is to convert one data type to another to allow developers to swap between Persistent, Network, and UI data objects. For example, when the PNMessageResult
is received by MessageService
, the data is converted to DBMessage
and stored in the database. Similarly to the previous example, when the user sends a message, the MessageUi.Data
object is converted to DBMessage
and stored in the local database. At the same time, the object is converted to NetworkMessage
and published in PubNub.
View models
The ViewModel
class is designed to store and manage UI-related data by communicating with services and repositories.
For more information on view models, refer to the official Android docs.
Services
The role of services is to communicate with PubNub APIs, add, get, or update objects and store them using local repositories. The default implementation of services exists in the ChatProvider
composition tree.
Data flow
The typical data flow through the components revolves around storing incoming data in the Room database instance and updating that data automatically in UI Components if the data matches the UI Components population query.
The PubNub APIs and events return network data that can be stored directly in the Room database using the repositories declared in
ChatProvider
.The UI Components use specific view models to connect with the database (persistent data) through repositories and Data Access Object instances. If the returned type of the
ViewModel
method is declared asFlow
, it automatically updates the component's data source if the new matching data is added, updated, or removed.The UI Components provide actions that can be configured to call PubNub APIs through
DataProvider
or update existing objects and store the result.
Channel
A channel is commonly viewed as a collection of associated users with a specific purpose or goal. A channel could be created for direct communication between two or more users, a group of users, or other use case-specific scenarios.
Persistent data
The default Persistent Object Model inside the Chat Components defines an Entity
named DBChannel
.
If a custom Persistent Object Model is used, then an Entity
must implement the Channel
interface before it can be used by the Chat Components framework.
To return all the members from a selected channel, the DBChannelWithMembers
object is implemented.
Default channel entity
The DBChannel
entity is defined with the following attributes:
Name | Type | Description |
---|---|---|
id | ChannelId | Unique identifier for the object. Used as a primary key. |
name | String | Name of the channel. |
description | String? | Channel details that can be displayed alongside the name. |
type | String | Functional type of the channel. Common examples are common and group . |
avatarURL | String? | Image that can be used to visually represent the channel. |
custom | ChannelCustomData? | Custom key value pairs that can be stored with the channel. |
updated | String? | Last time the remote object was changed. |
eTag | String? | Caching value that changes whenever the remote object changes. |
The ChannelCustomData
typealias is a map which stores additional key-value properties.
typealias ChannelCustomData = HashMap<String, Any>
Custom channel entity
To create a custom Persistent Object Model, you must implement the Channel
interface.
The following properties are required:
val id: ChannelId
val name: String
val description: String?
val updated: String?
val eTag: String?
val type: String
val avatarURL: String?
val custom: Any?
Relationships
The DBChannelWithMembers
entity contains the following attributes:
Name | Type | Description |
---|---|---|
channel | DBChannel | Entity of the channel object. |
members | List<DBMember> | List of members with membership in a selected channel. |
Repository
PubNub Chat Components for Android use a default channel repository named DefaultChannelRepository
.
If you want to use a custom Persistent Object Model, create a custom ViewModel
in which your Repository
implements the ChannelRepository
interface before it can be used by the Chat Components framework.
Default channel repository
The DefaultChannelRepository
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
channelDao | ChannelDao<DBChannel, DBChannelWithMembers> | Room's data access object (DAO) for the channel entity. |
The ChannelDao
interface uses both DBChannel
and DBChannelWithMembers
types. The first one is used for CRUD operations (Create, Read, Update, Delete) and the second one defines the type of the returned object.
Custom channel repository
To create a custom channel repository, implement the ChannelRepository
interface.
These are the required methods:
get()
Used to get one Channel
object with specified id
from the database. It returns null
when the object doesn't exist.
suspend fun get(id: ChannelId): Data?
getAll()
Returns the paginated source of channels.
fun getAll(
id: UserId? = null,
filter: Query? = null,
vararg sorted: Sorted = emptyArray(),
): PagingSource<Int, Data>
Name | Type | Description |
---|---|---|
id | UserId? | If specified, this method returns only channels the user is a member of. |
filter | Query? | Query filter for the database. |
sorted | Sorted | Array of sorted keys and directions. |
getList()
Returns the list of all channels.
Example:
val repository: DefaultChannelRepository = LocalChannelRepository.current
val channels = remember {
repository.getAll(
id = "my-user-id",
filter = Query("type LIKE ?", "group"),
sorted = arrayOf(Sorted("type", Sorted.Direction.ASC)),
)
}
suspend fun getList(): List<Data>
add()
Adds a new channel object to the database.
suspend fun add(vararg channel: DB)
remove()
Removes the channel object with the specified ID.
suspend fun remove(id: ChannelId)
size()
Returns the number of all channels.
suspend fun size(): Long
Network data
The PNChannelMetadata
class is used to communicate with PubNub APIs to send and receive channel metadata.
Name | Type | Description |
---|---|---|
id | String | Unique identifier for the object. |
name | String | Name of the channel. |
description | String? | Channel details that can be displayed alongside the name. |
custom | Any | Custom key value pairs that can be stored with the channel. |
updated | String? | Last time the remote object was changed. |
eTag | String? | Caching value that changes whenever the remote object changes. |
Custom channel data
PNChannelMetadata
contains the custom
property. You can use it to store a custom object. In the default implementation, NetworkChannelMapper
is implemented to parse the custom object to ChannelCustomData
.
Service
PubNub Chat Components for Android use a default service named DefaultChannelServiceImpl
. This service is responsible for synchronizing PubNub APIs network channel data and storing it in the local database.
You can obtain the instance or override it using LocalChannelService
in CompositionLocalProvider
. If you want to use a custom service, you need to create a class that implements the ChannelService
interface.
Default channel service
The DefaultChannelServiceImpl
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
pubNub | PubNub | PubNub instance. |
channelRepository | ChannelRepository<DBChannel, DBChannelWithMembers> | The ChannelRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on channel objects in the database. |
networkMapper | NetworkChannelMapper | Network object to database object mapper. |
errorHandler | ErrorHandler | Instance of the error handling class. |
coroutineScope | CoroutineScope | Scope for new coroutines. |
dispatcher | CoroutineDispatcher | Coroutine dispatcher implementation. |
Custom channel service
To create a custom channel service, implement the ChannelService
interface and pass it as a parameter to the composition tree. You can do it by wrapping ChatProvider
with CompositionLocalProvider
.
CompositionLocalProvider(LocalChannelService provides MyChannelServiceImpl){
ChatProvider(…)
}
These are the required methods:
bind()
Subscribes to the channels and sets listeners for channel events.
fun bind(vararg channels: String)
unbind()
Removes the listeners and unsubscribes from channels.
fun unbind()
getAll()
Fetches all the channel metadata from the PubNub API and stores it in the local repository.
fun getAll(
limit: Int? = null,
page: PNPage? = null,
filter: String? = null,
sort: Collection<PNSortKey> = listOf(),
includeCustom: Boolean = true,
)
add()
Sets channel metadata in the PubNub API.
fun add(channel: DBChannel, includeCustom: Boolean)
remove()
Removes channel metadata with a specified ID from the PubNub API.
fun remove(id: ChannelId)
Member
The users that are associated with a channel are also known as its members. A user might have many channel memberships and a channel might have multiple members.
Persistent data
The default Persistent Object Model inside the Chat Components defines an Entity
named DBMember
.
If a custom Persistent Object Model is used, then an Entity
must implement the Member
interface before it can be used by the Chat Components framework.
Default member entity
The DBMember
entity is defined with the following attributes:
Name | Type | Description |
---|---|---|
id | UserId | A unique identifier for the object. Used as a Primary Key |
name | String | Name of the user. |
email | String? | Email address of the user. |
externalId | String? | A unique external identifier for the user. |
profileUrl | String? | Image that can be used to visually represent the user. |
custom | CustomData? | Custom object to store a description of the user. |
updated | String? | Last time the remote object was changed. |
eTag | String? | Caching value that changes whenever the remote object changes. |
The CustomData
data class stores an additional description of the user.
data class CustomData(
val description: String
)
Custom member entity
To create a custom Persistent Object Model, you must implement the Member
interface.
The following properties are required:
val id: UserId
val name: String
val email: String?
val externalId: String?
val profileURL: String?
val updated: String?
val eTag: String?
val custom: Any?
Relationships
The DBMemberWithChannels
entity contains the following attributes:
Name | Type | Description |
---|---|---|
member | DBMember | Entity of a member object. |
channels | List<DBChannel> | List of channels with membership. |
Repository
PubNub Chat Components for Android use a default member repository named DefaultMemberRepository
.
If a custom Persistent Object Model is used, then a Repository
must implement the MemberRepository
interface before it can be used by the Chat Components framework. Also, you must implement a custom view model.
Default member repository
The DefaultMemberRepository
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
memberDao | MemberDao<DBMember, DBMemberWithChannels> | Room DAO for the member entity. |
The MemberDao
interface uses both DBMember
and DBMemberWithChannels
types. The first one is used for CRUD operations (Create, Read, Update, Delete) and the second one defines the type of the returned object.
Custom member repository
To create a custom member repository, you must implement a MemberRepository
interface.
The following methods are required:
get()
Used to get one member object with specified id
from the database. Returns null
when the object doesn't exist.
suspend fun get(id: UserId): Data?
getAll()
Returns paginated source of Members. Possible arguments are:
fun getAll(
id: ChannelId? = null,
filter: Query? = null,
vararg sorted: Sorted = emptyArray(),
): PagingSource<Int, Data>
Name | Type | Description |
---|---|---|
id | ChannelId? | If specified, returns all members of the channel with the specified ID. |
filter | Query? | Query filter for the database. |
sorted | Sorted | Array of sorted keys and directions. |
Example:
val repository: DefaultMemberRepository = LocalMemberRepository.current
val members = remember {
repository.getAll(
id = "channel-id",
filter = Query("name LIKE ?", "Android"),
sorted = arrayOf(Sorted("name", Sorted.Direction.ASC)),
)
}
getList()
If id
is specified, it returns the list of members of the specified channel. Otherwise, it returns the list of all members.
suspend fun getList(id: ChannelId? = null): List<Data>
add()
Adds a new member object to the database.
suspend fun add(vararg member: DB)
remove()
Removes the member object with the specified ID.
suspend fun remove(id: UserId)
size()
Returns the number of all members.
suspend fun size(): Long
Network data
The PNUUIDMetadata
class is used to communicate with PubNub APIs to send and receive user metadata.
Name | Type | Description |
---|---|---|
id | String | Unique identifier for the object. |
name | String? | Name of the user. |
email | String? | Email address of the user. |
externalId | String? | Unique external identifier for the user. |
profileUrl | String? | Image that can be used to visually represent the user. |
custom | Any? | Custom object to store extra data. |
updated | String? | Last time the remote object was changed. |
eTag | String? | Caching value that changes whenever the remote object changes. |
Custom member data
The PNUUIDMetadata
contains the custom
property. You can use it to store a custom object. In the default implementation, NetworkMemberMapper
is implemented to parse the custom object to CustomData
.
Service
PubNub Chat Components for Android use a default service named DefaultMemberServiceImpl
. This service is responsible for synchronizing PubNub APIs network member data and storing it in the local database.
You can obtain the instance or override it using LocalMemberService
in CompositionLocalProvider
. If you want to use a custom service, you need to create class which implements the MemberService
interface.
Default member service
The DefaultMemberServiceImpl
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
pubNub | PubNub | PubNub instance. |
memberRepository | MemberRepository<DBMember, DBMemberWithChannels> | The MemberRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on member objects in the database. |
networkMapper | NetworkMemberMapper | Network object to database object mapper. |
errorHandler | ErrorHandler | Instance of the error handling class. |
coroutineScope | CoroutineScope | Scope for new coroutines. |
dispatcher | CoroutineDispatcher | Coroutine dispatcher implementation. |
Custom member service
To create a custom member service, implement the MemberService
interface and pass it as a parameter to the composition tree. You can do it by wrapping ChatProvider
with CompositionLocalProvider
:
CompositionLocalProvider(LocalMemberService provides MyMemberServiceImpl){
ChatProvider(…)
}
These are the required methods:
bind()
Subscribes to the member channels and sets listeners for member metadata events.
fun bind(vararg channels: String)
unbind()
Removes the listeners and unsubscribes from member channels.
fun unbind()
getAll()
Fetches all the member metadata from the PubNub API and stores it in the local repository.
fun getAll(
limit: Int? = null,
page: PNPage? = null,
filter: String? = null,
sort: Collection<PNSortKey> = listOf(),
includeCustom: Boolean = true,
)
add()
Sets member metadata in the PubNub API.
fun add(member: DBMember, includeCustom: Boolean)
remove()
Removes member metadata with a specified ID from the PubNub API.
fun remove(id: MemberId)
Message
A message is the dynamic content that a user sends to other users inside a channel.
Persistent data
The default Persistent Object Model inside the Chat Components defines an Entity
named DBMessage
.
If a custom Persistent Object Model is used, then an Entity
must implement the Message
interface before it can be used by the Chat Components framework.
Default message entity
The DBMessage
entity is defined with the following attributes:
Name | Type | Description |
---|---|---|
id | String | Unique identifier for the object. Used as a primary key. |
type | String | Functional type of the message. Common examples are default or reply . Refer to NetworkMessage.Type for more information. |
text | String | Text of the message. |
attachment | List<DBAttachment>? | List of attachments. |
custom | Map<String, Any>? | Custom key value pairs that can be stored with the message. |
publisher | UserId | ID of the message publisher. |
channel | ChannelId | ID of the channel the message is published on. |
timetoken | Timetoken | Time when a message was published. |
isSent | Boolean | Information if a message was sent successfully. |
exception | String? | Error details when an exception is thrown. |
The DBAttachment
is an abstract class which is a supertype for the Image
, Link
, and Custom
classes:
@Keep
abstract class DBAttachment private constructor() : Attachment {
@Keep
data class Image(
val imageUrl: String,
override val type: String = "image",
override val custom: Any? = null
) : DBAttachment()
@Keep
data class Link(
val link: String,
override val type: String = "link",
override val custom: Any? = null
) : DBAttachment()
@Keep
data class Custom(
override val type: String = "custom",
override val custom: Any?
) : DBAttachment(
}
Custom message entity
To create a custom Persistent Object Model, you must implement the Message
interface.
The following properties are required:
val id: MessageId
val type: String
val text: String?
val attachment: List<Attachment>?
val custom: Any?
val publisher: UserId
val channel: ChannelId
val timetoken: Timetoken
val isSent: Boolean
var exception: String?
Additionally, the Attachment
interface has the following properties:
interface Attachment {
val type: String
val custom: Any?
}
Repository
PubNub Chat Components for Android use a default message repository named DefaultMessageRepository
.
If you want to use a custom Persistent Object Model, a Repository
must implement the MessageRepository
interface before it can be used by the Chat Components framework. Also, you must implement a custom view model.
Default message repository
The DefaultMessageRepository
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
messageDao | MessageDao<DBMessage, DBMessage> | Room DAO for the message entity. |
The MessageDao
interface uses the DBMessage
type for CRUD operations (Create, Read, Update, Delete) and to define the type of the returned object.
Custom message repository
To create a custom message repository, implement the MessageRepository
interface.
The following methods are required:
get()
Used to get one message object with a specified id
from the database. Returns null
when the object doesn't exist.
suspend fun get(id: MessageId): Data?
getList()
Returns the list of all messages from a specified id
.
suspend fun getList(
id: ChannelId,
count: Int,
before: Boolean,
timestamp: Long,
): List<Data>
These are the expected arguments:
Name | Type | Description |
---|---|---|
id | ChannelId | Specifies a channel to return messages from. |
count | Int | Specifies the number of messages to return. |
before | Boolean | Retrieves history messages before or after a specified timestamp . |
timestamp | Long | Gets messages before or after a given time, based on the value in the before field. |
getAll()
Returns a paginated source of channels.
fun getAll(
id: ChannelId? = null,
filter: Query? = null,
vararg sorted: Sorted = emptyArray(),
): PagingSource<Int, Data>
Possible arguments are:
Name | Type | Description |
---|---|---|
id | ChannelId? | If specified, it returns only messages from a specified ID. |
filter | Query? | Query filter for the database. |
sorted | Sorted | Array of sorted keys and directions. |
Example:
val repository: DefaultMessageRepository = LocalMessageRepository.current
val messages = remember {
repository.getAll(
id = "my-channel-id",
filter = Query("publisher LIKE ?", "my-uuid"),
sorted = arrayOf(Sorted("type", Sorted.Direction.ASC)),
)
}
getLast()
Returns the last message from a given channel.
suspend fun getLast(channelId: String): Data?
getLastByChannel()
Returns a flowable list of most recent messages (in a specified number) from a given channel.
fun getLastByChannel(id: ChannelId, count: Long): Flow<List<Data>>
Name | Type | Description |
---|---|---|
id | String | Channels to return messages from a specified ID. |
count | Long | Number of messages to return. |
hasMoreBefore()
Returns true
when any message with older timestamp than specified exists in database for a given channel.
suspend fun hasMoreBefore(id: ChannelId, timestamp: Timetoken): Boolean
hasMoreAfter()
Returns true
when any message with a newer timestamp than specified exists in a database for a given channel.
suspend fun hasMoreAfter(id: ChannelId, timestamp: Timetoken): Boolean
add()
Adds a new message object to the database.
suspend fun add(vararg message: DB)
remove()
Removes a message object from the database.
suspend fun remove(message: DB)
removeAll()
Removes all messages from a given channel.
suspend fun removeAll(id: ChannelId)
update()
Updates a specified message in the database.
suspend fun update(message: DB)
has()
Checks if there is a message with a specified ID.
suspend fun has(id: MessageId): Boolean
setSent()
Sets the message sent status.
suspend fun setSent(
id: MessageId,
timestamp: Timetoken? = null,
)
setSendingError()
Sets the message error status.
suspend fun setSendingError(
id: MessageId,
exception: String? = null,
timestamp: Timetoken? = null,
)
getLastTimestamp()
Returns the last known timestamp of the message in the channel.
suspend fun getLastTimestamp(id: ChannelId): Long
Network data
The PNMessageResult
class is used to communicate with PubNub APIs. The message
field is mapped to the NetworkMessage
class:
Name | Type | Description |
---|---|---|
id | String | Unique identifier for the object. |
type | String | Type of the message: "default" , "reply" , "ephemeral" , "error" , "system" , "custom" . |
text | String? | Content of the message. |
attachment | List<Attachment>? | List of attachments. |
custom | Any? | Custom key value pairs that can be stored with the channel. |
Custom message data
The NetworkMessage
contains custom
property. This one might be used to store custom object. In default implementation, the NetworkMessageMapper
and NetworkMessageHistoryMapper
are used to map it.
Service
PubNub Chat Components for Android use a default service named DefaultMessageServiceImpl
. This service is responsible for synchronizing PubNub APIs network message data and storing it in the local database.
You can obtain the instance or override it using LocalMessageService
in CompositionLocalProvider
. If you want to use a custom service, you need to create a class which implements the MessageService
interface.
Default message service
The DefaultMessageServiceImpl
implementation is defined with the following attributes:
Name | Type | Description |
---|---|---|
pubNub | PubNub | PubNub instance. |
messageRepository | essageRepository<DBMessage, DBMessage> | The MessageRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on message objects in the database. |
networkMapper | NetworkMessageMapper | Network object to database object mapper. |
networkHistoryMapper | NetworkMessageHistoryMapper | Network history object to database object mapper. |
errorHandler | ErrorHandler | Instance of the error handling class. |
coroutineScope | CoroutineScope | Scope for new coroutines. |
dispatcher | CoroutineDispatcher | Coroutine dispatcher implementation. |
Custom message service
To create a custom member service, implement the MessageService
interface and pass it as a parameter to the composition tree. You can do it by wrapping ChatProvider
with CompositionLocalProvider
:
CompositionLocalProvider(LocalMessageService provides MyMessageServiceImpl){
ChatProvider(…)
}
These are the required methods:
bind()
Sets listeners for message events.
fun bind()
unbind()
Removes the listeners.
fun unbind()
send()
Sends a message to a selected channel.
suspend fun send(
id: ChannelId,
message: Data,
meta: Any? = null,
store: Boolean = true,
onSuccess: (String, Timetoken) -> Unit = { message, result -> Timber.i("Message '$message' sent, result: $result") },
onError: (Exception) -> Unit = { Timber.i("Message sending error: $it") },
)
These are the expected arguments:
Name | Type | Description |
---|---|---|
id | ChannelId | Specifies a channel to send a message to. |
message | Data | Message data object to send. |
meta | Any? | Additional metadata to send. |
store | Boolean | The flag denoting to store a message in history. |
onSuccess | (String, Timetoken) -> Unit | Callback to invoke when a message is sent successfully. |
onError | (Exception) -> Unit | Callback to invoke when there is an error while sending a message. |
pullHistory()
Pulls the messages from the PubNub History API.
suspend fun pullHistory(
id: ChannelId,
start: Long?,
end: Long?,
count: Int,
withActions: Boolean = false,
withUUID: Boolean = false
)
These are the expected arguments:
Name | Type | Description |
---|---|---|
id | ChannelId | Specifies a channel to return history messages from. |
start | Long? | Delimits the start of a time slice (exclusive) to pull messages from. |
end | Long? | Delimits the end of a time slice (inclusive) to pull messages from. |
count | Int | Specifies the number of historical messages to return. |
withActions | Boolean | Retrieves history messages with message actions. |
withUUID | Boolean | Includes a publisher UUID with every history message. |