---
id: adding_custom_attachments
sidebar_position: 3
title: Adding Custom 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:

1) Rendering the attachment thumbnail in the `StreamMessageInput`

2) Sending a message with the custom attachment

3) 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:

![](../assets/location_sharing_example.jpg)

* 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:

```dart
Message(
  text: 'This is my location',
  attachments: [
    Attachment(
      uploadState: UploadState.success(),
      type: 'location',
      extraData: {
        'latitude': 'fetched_latitude',
        'longitude': 'fetched_longitude',
      },
    ),
  ],
)
```

For our example, we are going to use [geolocator](https://pub.dev/packages/geolocator) library.
Please check their [setup instructions](https://pub.dev/packages/geolocator) on their docs.

NOTE: If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned [here](https://stackoverflow.com/a/31238119/7489541).
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](https://developers.google.com/maps/documentation/maps-static/overview) to render the map in the message.
You can use other libraries as well such as [google_maps_flutter](https://pub.dev/packages/google_maps_flutter).

First, we add a button which when clicked fetches and shares location into the `MessageInput`:

```dart
StreamMessageInput(
  actions: [
    InkWell(
      child: Icon(
        Icons.location_on,
        size: 20.0,
        color: StreamChatTheme.of(context).colorTheme.grey,
      ),
      onTap: () {
       var channel = StreamChannel.of(context).channel;
       var user = StreamChat.of(context).user;

       _determinePosition().then((value) {
         channel.sendMessage(
           Message(
             text: 'This is my location',
             attachments: [
               Attachment(
                 uploadState: 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):

```dart
  String _buildMapAttachment(String lat, String long) {
    var baseURL = 'https://maps.googleapis.com/maps/api/staticmap?';
    var 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:

```dart
StreamMessageListView(
  messageBuilder: (context, details, messages, defaultMessage) {
    return defaultMessage.copyWith(
        customAttachmentBuilders: {
          'location': (context, message, attachments) {
            final attachmentWidget = Image.network(
              _buildMapAttachment(
                attachments[0].extraData['latitude'],
                attachments[0].extraData['longitude'],
              ),
            );

            return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0));
          }
        },
    );
  },
),
```

This gives us the final location attachment:

![](../assets/location_sharing_example_message.jpg)

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:

1) Add an attachment instead of sending a message

2) Customize the `StreamMessageInput`

First, we add the attachment when the location button is clicked:

```dart
 StreamMessageInputController _messageInputController = StreamMessageInputController();

 StreamMessageInput(
   messageInputController: _messageInputController,
   actions: [
     InkWell(
       child: Icon(
         Icons.location_on,
         size: 20.0,
         color: StreamChatTheme.of(context).colorTheme.grey,
        ),
      onTap: () {
        _determinePosition().then((value) {
           _messageInputController.addAttachment(
              Attachment(
                uploadState: 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:

```dart
StreamMessageInput(
  messageInputController: _messageInputController,
  actions: [
    InkWell(
      child: Icon(
        Icons.location_on,
        size: 20.0,
        color: StreamChatTheme.of(context).colorTheme.grey,
      ),
     onTap: () {
       _determinePosition().then((value) {
         _messageInputController.addAttachment(
           Attachment(
             uploadState: UploadState.success(),
             type: 'location',
             extraData: {
               'latitude': value.latitude.toString(),
               'longitude': value.longitude.toString(),
             },
           ),
         );
       }).catchError((err) {
          print('Error getting location!');
       });
     },
   ),
  ],
  attachmentThumbnailBuilders: {
    'location': (context, attachment) {
      return Image.network(
        _buildMapAttachment(
          attachment.extraData['latitude'],
          attachment.extraData['longitude'],
        ),
      );
    },
  },
),
```

And we can see the thumbnails in the StreamMessageInput:

![](../assets/location_sharing_example_message_thumbnail.jpg)
