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.
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:
Google’s official guide on maps
Google’s official sample apps using maps
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" /> |
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"/> |
Add a dependency for maps in the app level build.gradle file.
implementation 'com.google.android.gms:play-services-maps:17.0.0' |
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" /> |
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> |
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> |
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.
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) | |
} | |
} | |
} | |
} | |
} |
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 | |
} | |
} | |
} | |
} | |
} |
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)) | |
} | |
} | |
} |
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) | |
} | |
} | |
} |
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() | |
} | |
} |
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() | |
} | |
} | |
} |
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! 👩💻
Top comments (0)