val message: Message = Message(
cid = "messaging:general",
text = "Check out this product!",
extraData = mapOf(
"priority" to "high",
"product_id" to "12345",
"price" to 29.99,
),
)
channelClient.sendMessage(message).enqueue { result ->
when (result) {
is Result.Success -> { /* Message sent */ }
is Result.Failure -> { /* Handle error */ }
}
}Extra Data
Extra Data is additional information that can be added to the default data of Stream. It is a dictionary of key-value pairs that can be attached to messages, users, channels, and most domain models in the SDK.
On Android, Extra Data is represented as Map<String, Any>. This allows you to store strings, numbers, booleans, lists, and nested maps.
Adding Extra Data
You can add extra data when creating or updating messages, users, channels, and other models.
Messages
Add extra data when creating a message:
Users
Add extra data when updating the current user:
val user: User = User(
id = "user-id",
name = "John Doe",
extraData = mapOf(
"email" to "john.doe@example.com",
"phone" to "+1234567890",
"preferences" to mapOf(
"notifications" to true,
"theme" to "dark",
),
),
)
chatClient.updateUser(user).enqueue { result ->
when (result) {
is Result.Success -> { /* User updated */ }
is Result.Failure -> { /* Handle error */ }
}
}Channels
Add extra data when creating a channel:
chatClient.createChannel(
channelType = "messaging",
channelId = "travel-group",
memberIds = listOf("user-1", "user-2"),
extraData = mapOf(
"name" to "Travel Planning",
"destination" to "Paris",
"trip_date" to "2026-06-15",
),
).enqueue { result ->
when (result) {
is Result.Success -> { /* Channel created */ }
is Result.Failure -> { /* Handle error */ }
}
}Reading Extra Data
All domain models have an extraData property of type Map<String, Any>. You need to cast values to the expected type when reading:
// Reading simple values
val email: String? = user.extraData["email"] as? String
val priority: String? = message.extraData["priority"] as? String
val price: Double? = message.extraData["price"] as? Double
val productId: String? = message.extraData["product_id"] as? String
// Reading nested maps
val preferences: Map<String, Any>? = user.extraData["preferences"] as? Map<String, Any>
val theme: String? = preferences?.get("theme") as? String
val notificationsEnabled: Boolean? = preferences?.get("notifications") as? BooleanType-Safe Extensions
For cleaner code, create extension properties on the models you use:
val User.email: String?
get() = extraData["email"] as? String
val User.phone: String?
get() = extraData["phone"] as? String
val Message.priority: String?
get() = extraData["priority"] as? String
val Message.productId: String?
get() = extraData["product_id"] as? String
val Channel.destination: String?
get() = extraData["destination"] as? StringThen use them directly:
// Clean access via extensions
val email: String = user.email ?: ""
val priority: String = message.priority ?: "normal"
val destination: String = channel.destination ?: "Unknown"Advanced Example
For complex data structures, create data classes with conversion functions:
data class BookingInfo(
val flightNumber: String,
val departureDate: String,
val price: Double,
val passengers: List<Passenger>,
)
data class Passenger(
val name: String,
val age: Int,
)
// Convert to extraData map
fun BookingInfo.toExtraData(): Map<String, Any> = mapOf(
"flight_number" to flightNumber,
"departure_date" to departureDate,
"price" to price,
"passengers" to passengers.map { passenger ->
mapOf(
"name" to passenger.name,
"age" to passenger.age,
)
},
)
// Parse from extraData map
fun Map<String, Any>.toBookingInfo(): BookingInfo? {
val flightNumber: String = this["flight_number"] as? String ?: return null
val departureDate: String = this["departure_date"] as? String ?: return null
val price: Double = this["price"] as? Double ?: return null
val passengersData: List<Map<String, Any>> =
(this["passengers"] as? List<*>)?.filterIsInstance<Map<String, Any>>() ?: emptyList()
val passengers: List<Passenger> = passengersData.mapNotNull { data ->
val name: String = data["name"] as? String ?: return@mapNotNull null
val age: Int = (data["age"] as? Number)?.toInt() ?: return@mapNotNull null
Passenger(name, age)
}
return BookingInfo(flightNumber, departureDate, price, passengers)
}Then extend the Message model:
val Message.bookingInfo: BookingInfo?
get() = (extraData["booking"] as? Map<String, Any>)?.toBookingInfo()Use it when sending and reading messages:
// Sending a message with booking info
val booking = BookingInfo(
flightNumber = "AB123",
departureDate = "2026-06-15",
price = 450.00,
passengers = listOf(
Passenger("John Doe", 30),
Passenger("Jane Doe", 28),
),
)
val message: Message = Message(
cid = "messaging:travel",
text = "Here are our flight details!",
extraData = mapOf("booking" to booking.toExtraData()),
)
// Reading booking info from a message
val bookingInfo: BookingInfo? = message.bookingInfo
bookingInfo?.let {
println("Flight: ${it.flightNumber}")
println("Passengers: ${it.passengers.map { p -> p.name }}")
}