Stream's Android Chat SDK supports sending custom attachments with messages. In this tutorial, you'll learn how to send location data as a custom attachment.
Note: This tutorial assumes you already know the basics of the Stream API. To get started, check out the Android In-App Messaging Tutorial, and take a look at the Android SDK on GitHub.
Getting the Current Location
Before you send your attachment, first you'll need to get the current location of the user. The implementation for getting the current location is already set up, and you can see it in the sample project for this tutorial.
There's the LocationUtils
file which has a method with extends the FusedLocationProviderClient
class. The extension method returns a callbackFlow
with the location data.
To get the location you can collect the results in your Activity as shown below:
1234567lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { fusedLocationClient.locationFlow().collect { currentLocation = LatLng(it.latitude, it.longitude) } } }
Here, you're getting location from FusedLocationProviderClient
as a Flow
, using the locationFlow()
extension method. You're also collecting the results in a safe way using the Lifecycle
methods.
You now have the user's current location. In the next section, you'll see how to add location coordinates as custom attachments.
API key setup
To be able to load a map, you'll need to have your project on the Google Maps Platform console. From the console, you can get an API Key for your app which enables your app to access all map functionalities. Read more about this in the official documentation.
Once you have the API Key, you can add it in the local.properties
file as:
1googleMapsKey="YOUR_API_KEY"
Adding Location as a Custom Attachment
To add the location to your custom attachment, you need to create an Attachment
object as shown below.
123456789101112// 1 val attachment = Attachment( type = "location", extraData = mutableMapOf("latitude" to currentLocation.latitude, "longitude" to currentLocation.longitude), ) // 2 val message = Message( cid = channelId, text = "My current location", attachments = mutableListOf(attachment), )
To explain what the code above does:
- Here, you're creating an attachment with the custom
location
type. You'll use this key later to recognize and display attachments like this. You're also passing in the latitude and longitude from your location coordinates using theextraData
parameter which allows you to add arbitrary key-value pairs to an attachment. - You're adding your location attachment to a new
Message
using theattachments
property.
With this, you can send your message with this custom attachment.
Adding A Map Preview
The Android SDK renders previews for attachments like images and files by default. For custom attachments, you'll override the AttachmentViewFactory
class, which allows you to create and render your custom view for attachments.
First, create a layout for your custom attachment:
123456789101112131415<?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" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.gms.maps.MapView android:id="@+id/mapView" android:layout_width="match_parent" android:layout_height="200dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
The layout has a MapView
for displaying the location on the map.
Next you'll create the LocationAttachmentViewFactory
which extends AttachmentViewFactory
. This is how the class looks like:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263class LocationAttachmentViewFactory( private val lifecycleOwner: LifecycleOwner ): AttachmentViewFactory() { // 1 override fun createAttachmentView( data: MessageListItem.MessageItem, listeners: MessageListListenerContainer, style: MessageListItemStyle, parent: ViewGroup ): View { // 2 val location = data.message.attachments.find { it.type == "location" } return if (location != null) { val lat = location.extraData["latitude"] as Double val long = location.extraData["longitude"] as Double val latLng = LatLng(lat, long) // 3 createLocationView(parent, latLng) } else { super.createAttachmentView(data, listeners, style, parent) } } private fun createLocationView(parent: ViewGroup, location: LatLng): View { val binding = LocationAttachementViewBinding .inflate(LayoutInflater.from(parent.context), parent, false) // 4 val mapView = binding.mapView mapView.onCreate(Bundle()) // 5 mapView.getMapAsync { googleMap -> googleMap.setMinZoomPreference(18f) googleMap.moveCamera(CameraUpdateFactory.newLatLng(location)) } // 6 lifecycleOwner.lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun destroyMapView(){ mapView.onDestroy() } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun startMapView(){ mapView.onStart() } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun resumeMapView(){ mapView.onResume() } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun stopMapView(){ mapView.onStop() } }) return binding.root } }
Here's a breakdown of all the code above:
- This the method responsible for rendering all attachment UI. In this method, you only need to change the UI for attachments that have a location. The other attachments remain unchanged, and can use the default implementation.
- Here, you're getting the location data that you passed on your message using the
location
key that you defined earlier. - You're calling the
createLocationView
which is responsible for inflating the view. - Here you're initializing the
mapView
. - You're calling the
getMapAsync()
method. This method sets a callback object which is triggered when theGoogleMap
instance is ready for use. When that's invoked, you're updating the map with the a zoom level and moving it to the location you added to your attachment. - You're adding a
LifecycleObserver
. This is for calling the differentMapView
lifecycle methods depending on the lifecycle state ofLocationAttachmentViewFactory
. For example you're supposed to destoy theMapView
when the view has been destoyed. You achieve this by callingmapView.onDestroy()
when you receive theON_DESTROY
lifecycle event.
With the custom factory created, you now need to set up MessageListView
to use it. You do this as shown in the code below:
1binding.messageListView.setAttachmentViewFactory(LocationAttachmentViewFactory(lifecycleOwner = this))
You pass the Activity
as the lifecycleOwner
for the LocationAttachmentViewFactory
. This hooks the map behaviours to the lifecycle of the current Activity
.
With this, your app is ready to send and also preview custom location attachments. For the project, the action button for sending the location is on the options menu as shown in the image below.
You'll use the menu options to send the user's current location from the app to Stream Android Chat SDK. Once you tap on the location icon at the top right, it sends a message with the text: "My Current location" (the Message
object prepared earlier).
As seen from the image above, the attachment shows a map and TextView
. The map shows the location of the coordinates sent as custom attachments.
Conclusion
You've seen how easy it is to add a location as a custom attachment. You can now enrich your chat with location sharing.
The the full sample project with examples in this tutorial on GitHub.
You could take this idea further from here to implement live, continous location sharing as well, by editing the already sent message as the location of your device is updated. This would require some additional bookkeeping and lifecycle management, but Stream's Chat SDK supports it with its editing and realtime notification features.
You can learn more about the Android SDK by checking out its GitHub repository, and by taking a look at the documentation.
You can also go through our Custom Attachments Guide that explain more about custom attachments.