DEV Community

Cover image for How to implement map previews and location sharing on Android
Sendbird Devs
Sendbird Devs

Posted on • Originally published at sendbird.com on

How to implement map previews and location sharing on Android

By Alex Preston
Solutions Engineer | Sendbird

The post How to implement map previews and location sharing on Android appeared first on Sendbird.

Introduction

In-app location sharing and map previews offer an efficient way to communicate geolocation with friends and service providers. They remove any ambiguity about the directions to reach a location and provide peace of mind while allowing users to monitor the progress of a trip. For example, location sharing simplifies the life of delivery service personnel and improves their customer’s satisfaction when sending their location upon inquiry.

Mapping and messaging are fantastic ways to coordinate live interactions. Rory Sutherland, Vice Chairman of Ogilvy & Mather Group and author of Alchemy: The Dark Art and Curious Science of Creating Magic in Brands, Business, and Life, reminds us why the Uber map was magic. He called it:

“…a psychological moonshot, because it does not reduce the waiting time for a taxi but simply makes waiting 90% less frustrating.” – Rory Sutherland

As you can guess, this feature is specifically relevant for apps in the industries of:

  • Transportation and ridesharing
  • Delivery and logistics
  • Food or grocery delivery
  • Other consumer product delivery
  • Online to offline services

This simple tutorial will walk you through the implementation of location sharing with easily digestible within-message map previews.

Please note that this guide assumes you have already implemented chat using the Sendbird SDK. If you have not, check out the docs or this tutorial to learn how to do this.

Let’s begin! 💻

Implementing location sharing and map previews in Sendbird

When a user presses the “Share Location” button, Android’s LocationManager class provides the user’s geographic coordinates. Sendbird sends their location in a UserMessage with the CustomType set to location. After sending the message, it is inserted into the RecyclerView through its adapter. Based on the MessageType(UserMessage) and the CustomType(location), the layoutInflater inflates a custom view containing the MapView. Finally, you add geographic coordinates to the MapView.

Please note that using the Google Play Service Maps SDK, users can share their location from within the in-app chat and the recipient can open this location in Google Maps. Sendbird displays the map in an easily digestible preview for quick consumption.

alt text

To implement location sharing and map previews, follow these steps:

  • Understand the prerequisites (outlined below) for location sharing and map previews on Android
  • Obtain permission to use the user’s location
  • Declare your API access token and a dependency for maps
  • Create the layout for sender and recipient
  • Retrieve the user’s location
  • Send the user’s location in a UserMessage
  • Handle the received message by checking the custom type and bind it to a view

Prerequisites for location sharing and map previews

To reiterate, this guide assumes that you have already implemented chat using the Sendbird SDK. Check out the docs or this tutorial for more information.

Note that the code in this guide uses Sendbird’s SyncManager SDK to send and receive messages.

You will need:

Note:

  • If you are using Android’s built-in emulator, note that it will likely not show your location.

Other resources:

Step 1: Obtain permission to use the user’s location

For location sharing to work, you need to obtain permission to use the user’s location.

Request this at runtime. For the sake of brevity, this guide omits the runtime code.

Instead, it shows permission as it is shown in the AndroidManifest.xml.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Gist

Step 2: Declare your API access token and add a dependency for maps

Declare your API access token in the AndroidManifest.xml.

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR API KEY HERE"/>

Gist

Add a dependency for maps in the app level build.gradle file.

implementation 'com.google.android.gms:play-services-maps:17.0.0'
view raw build.gradle hosted with ❤ by GitHub

Gist

Step 3: Create the layout for the sender and recipient

Create a layout for both sides of the chat, i.e. both sender and recipient. This guide differentiates between them as “me” and “other,” where “me” represents the sender and “other” represents the recipient. Both should contain a MapView.

<com.google.android.gms.maps.MapView
android:id="@+id/map_gchat_me"
android:layout_width="@dimen/thumbnail_width"
android:layout_height="@dimen/thumbnail_height"
map:liteMode="true" //Used to display the icons for “Directions”, and “Location” on View
map:mapType="none" />

Gist

See the full gist for each layout.

Me (sender):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin">
<TextView
android:id="@+id/text_gchat_map_date_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin_x2"
android:background="@drawable/group_chat_date_background"
android:paddingStart="@dimen/date_horizontal_padding"
android:paddingTop="@dimen/date_vertical_padding"
android:paddingEnd="@dimen/date_horizontal_padding"
android:paddingBottom="@dimen/date_vertical_padding"
android:text="@string/date_placeholder"
android:textColor="@color/groupChatDateText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.cardview.widget.CardView
android:id="@+id/card_gchat_map_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:background="@drawable/group_chat_file_background"
app:cardCornerRadius="@dimen/card_corners"
app:cardElevation="@dimen/card_elevation"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_gchat_map_date_me">
<com.google.android.gms.maps.MapView
android:id="@+id/map_gchat_me"
android:layout_width="@dimen/thumbnail_width"
android:layout_height="@dimen/thumbnail_height"
map:liteMode="true"
map:mapType="none" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/text_gchat_map_read_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_count_placeholder"
android:textColor="@color/groupChatReadReceiptOther"
android:textSize="@dimen/text_message_date_size"
app:layout_constraintBottom_toTopOf="@+id/text_gchat_map_timestamp_me"
app:layout_constraintEnd_toStartOf="@id/card_gchat_map_me" />
<TextView
android:id="@+id/text_gchat_map_timestamp_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_time_placeholder"
android:textColor="@color/groupChatDateBackground"
android:textSize="@dimen/text_message_date_size"
app:layout_constraintBottom_toBottomOf="@+id/card_gchat_map_me"
app:layout_constraintEnd_toStartOf="@+id/card_gchat_map_me" />
</androidx.constraintlayout.widget.ConstraintLayout>

Gist

Other (recipient):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_gchat_map_date_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin_x2"
android:background="@drawable/group_chat_date_background"
android:paddingStart="@dimen/date_horizontal_padding"
android:paddingTop="@dimen/date_vertical_padding"
android:paddingEnd="@dimen/date_horizontal_padding"
android:paddingBottom="@dimen/date_vertical_padding"
android:text="@string/date_placeholder"
android:textColor="@color/groupChatDateText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_gchat_map_profile_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:contentDescription="@string/user_icon"
android:src="@drawable/profile_placeholder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_gchat_map_date_other" />
<TextView
android:id="@+id/text_gchat_map_user_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/profile_margin"
android:text="@string/name_placeholder"
android:textSize="@dimen/text_standard"
app:layout_constraintStart_toEndOf="@+id/image_gchat_map_profile_other"
app:layout_constraintTop_toBottomOf="@+id/text_gchat_map_date_other" />
<androidx.cardview.widget.CardView
android:id="@+id/card_gchat_map_message_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/card_corners"
app:cardElevation="@dimen/card_elevation"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:layout_constraintStart_toEndOf="@+id/image_gchat_map_profile_other"
app:layout_constraintTop_toBottomOf="@+id/text_gchat_map_user_other">
<com.google.android.gms.maps.MapView
android:id="@+id/map_gchat_other"
android:layout_width="@dimen/thumbnail_width"
android:layout_height="@dimen/thumbnail_height"
map:liteMode="true"
map:mapType="none" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/text_gchat_map_read_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_count_placeholder"
android:textColor="@color/groupChatReadReceiptOther"
android:textSize="@dimen/text_message_date_size"
app:layout_constraintBottom_toTopOf="@+id/text_gchat_map_timestamp_other"
app:layout_constraintStart_toEndOf="@id/card_gchat_map_message_other" />
<TextView
android:id="@+id/text_gchat_map_timestamp_other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_time_placeholder"
android:textColor="@color/groupChatDateBackground"
android:textSize="@dimen/text_message_date_size"
app:layout_constraintBottom_toBottomOf="@+id/card_gchat_map_message_other"
app:layout_constraintStart_toEndOf="@+id/card_gchat_map_message_other" />
</androidx.constraintlayout.widget.ConstraintLayout>

Gist

Note: This guide skips over the implementation for setting up the recyclerview, setting button listeners, etc. You can view the sample code in this gist.

Step 4: Retrieve the user’s location

Retrieve the geographic coordinates from the LocationManager. This happens when the user decides to share their location.

override fun shareLocation(context: Context) {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val longitude = location.longitude
val latitude = location.latitude
val result = "$longitude,$latitude"
val params = UserMessageParams()
params.setMessage(result)
params.setCustomType("location")
if (channel != null) {
channel!!.sendUserMessage(params) { userMessage, e ->
if (e != null) {
//Error Handling
} else {
messageCollection?.appendMessage(userMessage as BaseMessage)
}
}
}
}

Gist

Step 5: Send the user’s location in a UserMessage

Once you obtain the longitude and latitude, send it as a UserMessage. To indicate that it is a location, set the CustomType on the individual message to “location.”

Next, insert the message into the RecyclerView by adding it to the adapter.

See below for the full implementation.

class GroupChannelChatPresenterImpl @Inject constructor(private val preferenceHelper: AppPreferenceHelper) :
GroupChannelChatPresenter {
private lateinit var view: GroupChannelChatView
private lateinit var channelUrl: String
private lateinit var message: String
private var channel: GroupChannel? = null
private lateinit var context: Context
private var messageCollection: MessageCollection? = null
private val messageFilter = MessageFilter(BaseChannel.MessageTypeFilter.ALL, null, null)
override fun setView(view: GroupChannelChatView) {
this.view = view
}
override fun onResume(context: Context, channelUrl: String) {
this.context = context
val userId = preferenceHelper.getUserId()
this.channelUrl = channelUrl
SendBirdSyncManager.setup(context, userId) {
(context as Activity).runOnUiThread {
if (!SendBird.getConnectionState().equals(SendBird.ConnectionState.OPEN)) {
refresh()
}
createMessageCollection(channelUrl)
ConnectionUtil.addConnectionManagementHandler(
AppConstants.CONNECTION_HANDLER_ID,
userId,
object : ConnectionUtil.ConnectionManagementHandler {
override fun onConnected(connected: Boolean) {
refresh()
}
})
}
}
}
override fun refresh() {
if (channel != null) {
channel!!.refresh {
if (it != null) {
it.printStackTrace() // ADD MORE error handling
return@refresh
}
//TODO update UI components
}
if (messageCollection != null) {
messageCollection!!.fetch(MessageCollection.Direction.NEXT, null)
}
} else {
createMessageCollection(channelUrl)
}
}
@SuppressLint("MissingPermission") //Should obviously implicitly check permissions and not assume they have been given. (Declared in manifest for test sake)
override fun shareLocation(context: Context) {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val longitude = location.longitude
val latitude = location.latitude
val result = "$longitude,$latitude"
val params = UserMessageParams()
params.setMessage(result)
params.setCustomType("location")
if (channel != null) {
channel!!.sendUserMessage(params) { userMessage, e ->
if (e != null) {
view.showValidationMessage(1)
} else {
messageCollection?.appendMessage(userMessage as BaseMessage)
}
}
}
}
private fun createMessageCollection(channelUrl: String) {
if (SendBird.getConnectionState() != SendBird.ConnectionState.OPEN) {
MessageCollection.create(channelUrl, messageFilter, Long.MAX_VALUE) { collection, e ->
if (e == null) {
if (messageCollection == null) {
messageCollection = collection
messageCollection?.setCollectionHandler(messageCollectionHandler)
channel = messageCollection?.channel
messageCollection?.fetch(MessageCollection.Direction.PREVIOUS) {
if (it != null) {
return@fetch
}
(context as Activity).runOnUiThread() {
view.markAllRead()
}
}
}
}
}
} else {
GroupChannel.getChannel(channelUrl) { groupChannel, e ->
if (e == null) {
channel = groupChannel
if (messageCollection == null) {
messageCollection = MessageCollection(groupChannel, messageFilter, Long.MAX_VALUE)
messageCollection?.setCollectionHandler(messageCollectionHandler)
messageCollection?.fetch(MessageCollection.Direction.PREVIOUS) {
if (it != null) {
return@fetch
}
(context as Activity).runOnUiThread() {
view.markAllRead()
}
}
}
}
}
}
}
private val messageCollectionHandler = MessageCollectionHandler { collection, messages, action ->
Log.d("SyncManager", "onMessageEvent: size = " + messages.size + ", action = " + action)
(context as Activity).runOnUiThread() {
when (action) {
MessageEventAction.INSERT -> {
val title = channel!!.members[0].nickname + ", " + channel!!.members[1].nickname + "..."
view.displayChatTitle(title)
view.insert(messages)
}
MessageEventAction.REMOVE -> {
view.remove(messages)
}
MessageEventAction.UPDATE -> {
view.update(messages)
}
MessageEventAction.CLEAR -> {
view.clear()
}
else -> {
view.showValidationMessage(1)
}
}
}
}
}

Gist

Step 6: Bind the view to your customer viewHolders

After adding the messages to the adapter, check the message type before binding the view. Do this by overriding getItemViewType.

override fun getItemViewType(position: Int): Int {
val message = messages.get(position)
when (message) {
is UserMessage -> {
return if (message.sender.userId == SendBird.getCurrentUser().userId) {
if (message.customType == "location") {
AppConstants.VIEW_TYPE_LOCATION_ME
}
} else {
if (message.customType == "location") {
AppConstants.VIEW_TYPE_LOCATION_OTHER
}
}
}
}
}

Gist

Based on what getItemViewType returns, inflate the layout according to whether the message belongs to “me” (sender) or “other” (recipient).

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
when (viewType) {
AppConstants.VIEW_TYPE_LOCATION_ME -> {
return MyLocationHolder(layoutInflater.inflate(R.layout.item_gchat_map_me, parent, false))
}
AppConstants.VIEW_TYPE_LOCATION_OTHER -> {
return OtherLocationHolder(layoutInflater.inflate(R.layout.item_gchat_map_other, parent, false))
}
}
}

Gist

Next, bind the views to your custom viewHolder.

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val message = messages.get(position)
var isNewDay = false
when {
(position < messages.size - 1) -> {
val previousMessage = messages.get(position + 1)
isNewDay = !DateUtil.isSameDay(message.createdAt, previousMessage.createdAt)
}
(position == messages.size - 1) -> {
isNewDay = true
}
}
when (holder.itemViewType) {
AppConstants.VIEW_TYPE_LOCATION_ME -> {
holder as MyLocationHolder
holder.bindView(context, messages.get(position) as UserMessage, isNewDay)
}
AppConstants.VIEW_TYPE_LOCATION_OTHER -> {
holder as OtherLocationHolder
holder.bindView(context, messages.get(position) as UserMessage, isNewDay)
}
}
}

Gist

In your custom viewHolder, implement OnMapReadyCallback, and override the onMapReady function.

private val mapView = view.map_gchat_me
/** Initialises the MapView by calling its lifecycle methods */
init {
with(mapView) {
// Initialise the MapView
onCreate(null)
// Set the map ready callback to receive the GoogleMap object
getMapAsync(this@MyLocationHolder)
}
}
fun bindView(context: Context, message: UserMessage, isNewDay: Boolean) {
this.context = context
val cords = message.message.split(",")
longitude = cords[0].toDouble()
latitude = cords[1].toDouble()
latLng = LatLng(latitude, longitude)
setLocation()
}
override fun onMapReady(googleMap: GoogleMap?) {
MapsInitializer.initialize(context.applicationContext)
map = googleMap ?: return
setLocation()
}
private fun setLocation() {
if (!::map.isInitialized) return
with(map) {
moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 11f))
addMarker(MarkerOptions().position(latLng))
mapType = GoogleMap.MAP_TYPE_NORMAL
}
mapView.onResume()
}
}

Gist

You may find it useful to check out the full Adapter code.

class GroupChannelChatAdapter(context: Context, listener: OnItemClickListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var messages: MutableList<BaseMessage>
private var context: Context
private var listener: OnItemClickListener
init {
messages = ArrayList()
this.context = context
this.listener = listener
}
fun insert(messageList: MutableList<BaseMessage>) {
for (message in messageList) {
val index = SyncManagerUtil.findIndexOfMessage(messages, message)
this.messages.add(index, message)
notifyItemInserted(index)
}
}
fun update(messageList: MutableList<BaseMessage>) {
for (message in messageList) {
val index = SyncManagerUtil.findIndexOfMessage(messages, message)
if (index != -1) {
this.messages.add(index, message)
notifyItemChanged(index)
}
}
}
fun remove(messageList: MutableList<BaseMessage>) {
for (message in messageList) {
val index = SyncManagerUtil.findIndexOfMessage(messages, message)
if (index != -1) {
this.messages.removeAt(index)
notifyItemRemoved(index)
}
}
}
fun clear() {
messages.clear()
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
when (viewType) {
AppConstants.VIEW_TYPE_LOCATION_ME -> {
return MyLocationHolder(layoutInflater.inflate(R.layout.item_gchat_map_me, parent, false))
}
AppConstants.VIEW_TYPE_LOCATION_OTHER -> {
return OtherLocationHolder(layoutInflater.inflate(R.layout.item_gchat_map_other, parent, false))
}
}
}
override fun getItemViewType(position: Int): Int {
val message = messages.get(position)
when (message) {
is UserMessage -> {
return if (message.sender.userId == SendBird.getCurrentUser().userId) {
if (message.customType == "location") {
AppConstants.VIEW_TYPE_LOCATION_ME
}
} else {
if (message.customType == "location") {
AppConstants.VIEW_TYPE_LOCATION_OTHER
}
}
}
else -> return -1
}
}
override fun getItemCount() = messages.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val message = messages.get(position)
var isNewDay = false
when {
(position < messages.size - 1) -> {
val previousMessage = messages.get(position + 1)
isNewDay = !DateUtil.isSameDay(message.createdAt, previousMessage.createdAt)
}
(position == messages.size - 1) -> {
isNewDay = true
}
}
when (holder.itemViewType) {
AppConstants.VIEW_TYPE_LOCATION_ME -> {
holder as MyLocationHolder
holder.bindView(context, messages.get(position) as UserMessage, isNewDay)
}
AppConstants.VIEW_TYPE_LOCATION_OTHER -> {
holder as OtherLocationHolder
holder.bindView(context, messages.get(position) as UserMessage, isNewDay)
}
}
}
}
itemView.setOnClickListener {
listener.onFileMessageClicked(message)
}
}
}
class MyLocationHolder(view: View) : RecyclerView.ViewHolder(view), OnMapReadyCallback {
private lateinit var map: GoogleMap
private lateinit var context: Context
private lateinit var latLng: LatLng
private var longitude = 0.0
private var latitude = 0.0
private val mapView = view.map_gchat_me
private val date = view.text_gchat_map_date_me
private val timestamp = view.text_gchat_map_timestamp_me
/** Initialises the MapView by calling its lifecycle methods */
init {
with(mapView) {
// Initialise the MapView
onCreate(null)
// Set the map ready callback to receive the GoogleMap object
getMapAsync(this@MyLocationHolder)
}
}
fun bindView(context: Context, message: UserMessage, isNewDay: Boolean) {
this.context = context
timestamp.text = DateUtil.formatTime(message.createdAt)
val cords = message.message.split(",")
longitude = cords[0].toDouble()
latitude = cords[1].toDouble()
latLng = LatLng(latitude, longitude)
if (isNewDay) {
date.visibility = View.VISIBLE
date.text = DateUtil.formatDate(message.createdAt)
} else {
date.visibility = View.GONE
}
setLocation()
}
override fun onMapReady(googleMap: GoogleMap?) {
MapsInitializer.initialize(context.applicationContext)
map = googleMap ?: return
setLocation()
}
private fun setLocation() {
if (!::map.isInitialized) return
with(map) {
moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 11f))
addMarker(MarkerOptions().position(latLng))
mapType = GoogleMap.MAP_TYPE_NORMAL
}
mapView.onResume()
}
}
class OtherLocationHolder(view: View) : RecyclerView.ViewHolder(view), OnMapReadyCallback {
private lateinit var map: GoogleMap
private lateinit var context: Context
private lateinit var latLng: LatLng
private var longitude = 0.0
private var latitude = 0.0
private val mapView = view.map_gchat_other
private val date = view.text_gchat_map_date_other
private val timestamp = view.text_gchat_map_timestamp_other
private val profileImage = view.image_gchat_map_profile_other
private val username = view.text_gchat_map_user_other
/** Initialises the MapView by calling its lifecycle methods */
init {
with(mapView) {
// Initialise the MapView
onCreate(null)
// Set the map ready callback to receive the GoogleMap object
getMapAsync(this@OtherLocationHolder)
}
}
fun bindView(context: Context, message: UserMessage, isNewDay: Boolean) {
this.context = context
val cords = message.message.split(",")
longitude = cords[0].toDouble()
latitude = cords[1].toDouble()
latLng = LatLng(latitude, longitude)
username.text = message.sender.nickname
timestamp.text = DateUtil.formatTime(message.createdAt)
Glide.with(context).load(message.sender.profileUrl).apply(RequestOptions().override(75, 75))
.into(profileImage)
if (isNewDay) {
date.visibility = View.VISIBLE
date.text = DateUtil.formatDate(message.createdAt)
} else {
date.visibility = View.GONE
}
setLocation()
}
override fun onMapReady(googleMap: GoogleMap?) {
MapsInitializer.initialize(context.applicationContext)
map = googleMap ?: return
setLocation()
}
private fun setLocation() {
if (!::map.isInitialized) return
with(map) {
moveCamera(com.google.android.gms.maps.CameraUpdateFactory.newLatLngZoom(latLng, 11f))
addMarker(com.google.android.gms.maps.model.MarkerOptions().position(latLng))
mapType = com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL
}
mapView.onResume()
}
}
}

Gist

Conclusion

And that’s a wrap! With Sendbird’s map preview and location sharing implementation, your users will now be able to send a map showing their location, thereby improving communication with others and making their app experience a bit more magical.

Happy coding! 👩‍💻

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay