Message(
text: 'This is my location',
attachments: [
Attachment(
uploadState: const UploadState.success(),
type: 'location',
extraData: const {
'latitude': 'fetched_latitude',
'longitude': 'fetched_longitude',
},
),
],
)
Attachments
Adding Your Own Types Of Attachments To A Message
Introduction
Stream Chat supports attachment types like images, video and files by default. You can also add your own types of attachments through the SDK such as location, audio, etc.
This involves doing three things:
Rendering the attachment thumbnail in the
StreamMessageInput
Sending a message with the custom attachment
Rendering the custom message attachment
To do this, let’s check out an example to add location sharing to Stream Chat.
Location Sharing
Let’s build an example of location sharing option in the app:
Show a “Share Location” button next to StreamMessageInput
Textfield
.When the user presses this button, it should fetch the current location coordinates of the user, and send a message on the channel as follows:
For our example, we are going to use geolocator
library.
Please check their setup instructions on their docs.
NOTE: If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned here. Also don’t forget to enable “location update” capability in background mode, from XCode.
On the receiver end, location
type attachment should be rendered in map view, in the StreamMessageListView
.
We are going to use Google Static Maps API to render the map in the message.
You can use other libraries as well such as google_maps_flutter
.
First, we add a button which when clicked fetches and shares location into the MessageInput
:
StreamMessageInput(
actions: [
InkWell(
child: const Icon(
Icons.location_on,
size: 20,
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
),
onTap: () {
final channel = StreamChannel.of(context).channel;
_determinePosition().then((value) {
channel.sendMessage(
Message(
text: 'This is my location',
attachments: [
Attachment(
uploadState: const UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
],
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
),
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
if (permission == LocationPermission.denied) {
return Future.error(
'Location permissions are denied');
}
}
return await Geolocator.getCurrentPosition();
}
Next, we build the Static Maps URL (Add your API key before using the code snippet):
String _buildMapAttachment(String lat, String long) {
final url = Uri(
scheme: 'https',
host: 'maps.googleapis.com',
port: 443,
path: '/maps/api/staticmap',
queryParameters: {
'center': '${lat},${long}',
'zoom': '15',
'size': '600x300',
'maptype': 'roadmap',
'key': 'YOUR_API_KEY',
'markers': 'color:red|${lat},${long}'
});
return url.toString();
}
And then modify the StreamMessageListView
and tell it how to build a location attachment, using the messageBuilder
property and copying the default message implementation overriding the customAttachmentBuilders
property:
StreamMessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
customAttachmentBuilders: {
'location': (context, message, attachments) {
final attachmentWidget = Image.network(
_buildMapAttachment(
attachments[0].extraData['latitude'].toString(),
attachments[0].extraData['longitude'].toString(),
),
);
return WrapAttachmentWidget(
attachmentWidget: attachmentWidget,
attachmentShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
);
}
},
);
},
),
This gives us the final location attachment:
Additionally, you can also add a thumbnail if a message has a location attachment (unlike in this case, where we sent the message directly).
To do this, we will:
Add an attachment instead of sending a message
Customize the
StreamMessageInput
First, we add the attachment when the location button is clicked:
StreamMessageInputController _messageInputController = StreamMessageInputController();
StreamMessageInput(
messageInputController: _messageInputController,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20,
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
),
onTap: () {
_determinePosition().then((value) {
_messageInputController.addAttachment(
Attachment(
uploadState: const UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
),
After this, we can build the thumbnail:
StreamMessageInput(
messageInputController: _messageInputController,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20,
color: StreamChatTheme.of(context).colorTheme.textLowEmphasis,
),
onTap: () {
_determinePosition().then((value) {
_messageInputController.addAttachment(
Attachment(
uploadState: const UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
mediaAttachmentBuilder: (
BuildContext context,
Attachment attachment,
ValueSetter<Attachment>? onRemovePressed,
) {
if (attachment.type == 'location') {
return Image.network(
_buildMapAttachment(
attachment.extraData['latitude'].toString(),
attachment.extraData['longitude'].toString(),
),
);
}
return const SizedBox();
},
),
And we can see the thumbnails in the StreamMessageInput: