Send and Receive Images

Overview

This guide covers sending and receiving images within a conversation.

Before you begin, make sure you added the SDK to your app and you are able to create a conversation.

NOTE: A step-by-step tutorial to build a chat application is available here.

This guide will make use of the following concepts:

Send an Image

Given a conversation you are already a member of:

const fileInput = document.getElementById('fileInput');
conversation.sendImage(fileInput.files[0]).then((imageRequest) => {
  ...
}).catch(errorLogger)
fun sendImage(file: File){
    client.uploadAttachment(file, object : NexmoRequestListener<NexmoImage> {
        override fun onSuccess(image: NexmoImage?) {
            val message = image?.original?.let { NexmoMessage.fromImage(it.url) }
            if (message != null) {
                conversation?.sendMessage(message, object: NexmoRequestListener<Void?> {
                    override fun onError(apiError: NexmoApiError) {
                        Log.d("TAG", "Error: failed to send message, ${apiError.message}")
                    }
                    override fun onSuccess(aVoid: Void?) {}
                })
            }
        }
        override fun onError(error: NexmoApiError) {
            Log.d("TAG", "Error: Image not uploaded, ${error.message}")
        }
    })
}
public void sendImage(File file){
    client.uploadAttachment(file, new NexmoRequestListener<NexmoImage>() {
        @Override
        public void onSuccess(@Nullable NexmoImage result) {
            if (result != null){
                NexmoMessage message = NexmoMessage.fromImage(result.getOriginal().getUrl());
                conversation.sendMessage(message, new NexmoRequestListener<Void>() {
                    @Override
                    public void onError(@NonNull NexmoApiError error) {
                        Log.d("TAG", "Error: failed to send message, " + error.getMessage());
                    }
                    @Override
                    public void onSuccess(@Nullable Void result) {}
                });
            }
        }
        @Override
        public void onError(@NonNull NexmoApiError error) {
            Log.d("TAG", "Error: Image not uploaded, " + error.getMessage());
        }
    });
    }
let image = UIImage(named: "file.png")
guard let imageData = image?.pngData() else { return }

client.uploadAttachment(with: .image, name: "File name", data: imageData) { error, data in
    if let error = error {
        print("Error sending image: \(error.localizedDescription)")
        return
    }

    if let imageObject = data?["original"] as? [String: Any],
        let imageUrl = imageObject["url"] as? String {
        let imageMessage = NXMMessage(imageUrl: imageUrl)
        conversation.sendMessage(imageMessage, completionHandler: { [weak self] (error) in
            ...
        })
    }
}
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"file.png"]);

[self.client uploadAttachmentWithType:NXMAttachmentTypeImage
                                    name:@"File name 2"
                                    data:imageData
                    completionHandler:^(NSError * _Nullable error, NSDictionary * _Nullable data) {
    if (error == nil) {
        NSLog(@"Error sending image");
    }

    NSString *imageUrl = [data valueForKeyPath:@"original.url"];
    NXMMessage *imageMessage = [[NXMMessage alloc] initWithImageUrl:imageUrl];

    [conversation sendMessage:message completionHandler:^(NSError * _Nullable error) {
        ...
    }];
}];

Receive an Image URL

A image conversation event will be received when a member sends an image to a conversation:

conversation.on('image', (sender, event) => {
  console.log('*** Image received', sender, event);
});
private val messageListener = object : NexmoMessageEventListener {
    override fun onTypingEvent(typingEvent: NexmoTypingEvent) {}

    override fun onMessageEvent(messageEvent: NexmoMessageEvent) {
        val userName = messageEvent.embeddedInfo.user.name
        val imageURL = messageEvent.message.imageUrl
        Log.d("TAG", "Message received. User $userName : $imageURL")
    }

    override fun onAttachmentEvent(attachmentEvent: NexmoAttachmentEvent) {}

    override fun onTextEvent(textEvent: NexmoTextEvent) {}

    override fun onSeenReceipt(seenEvent: NexmoSeenEvent) {}

    override fun onEventDeleted(deletedEvent: NexmoDeletedEvent) {}

    override fun onDeliveredReceipt(deliveredEvent: NexmoDeliveredEvent) {}
}

conversation?.addMessageEventListener(messageListener)
private NexmoMessageEventListener messageListener = new NexmoMessageEventListener() {
    @Override
    public void onTextEvent(@NonNull NexmoTextEvent textEvent) {}

    @Override
    public void onMessageEvent(@NonNull NexmoMessageEvent messageEvent) {
        String userName = messageEvent.getEmbeddedInfo().getUser().getName();
        String imageURL = messageEvent.getMessage().getImageUrl();

        Log.d("TAG", "Message received. User " + userName + " : " + imageURL);
    }

    @Override
    public void onAttachmentEvent(@NonNull NexmoAttachmentEvent attachmentEvent) {}

    @Override
    public void onEventDeleted(@NonNull NexmoDeletedEvent deletedEvent) {}

    @Override
    public void onSeenReceipt(@NonNull NexmoSeenEvent seenEvent) {}

    @Override
    public void onDeliveredReceipt(@NonNull NexmoDeliveredEvent deliveredEvent) {}

    @Override
    public void onTypingEvent(@NonNull NexmoTypingEvent typingEvent) {}
};

conversation.addMessageEventListener(messageListener);

Add NXMConversationDelegate as an extension to a ViewController or similar, and implement conversation(_ conversation: NXMConversation, didReceive event: NXMMessageEvent):

Note: The first method below is required when implementing NXMConversationDelegate:

extension ViewController: NXMConversationDelegate {
    func conversation(_ conversation: NXMConversation, didReceive error: Error) {
        NSLog("Conversation error: \(error.localizedDescription)")
    }
    func conversation(_ conversation: NXMConversation, didReceive event: NXMMessageEvent) {
        NSLog("Received image: \(event.imageUrl)")
    }
}

Have a ViewController, or similar, conform to NXMConversationDelegate and implement conversation:didReceiveMessageEvent::

Note: The first method below is required when implementing NXMConversationDelegate:

- (void)conversation:(NXMConversation *)conversation didReceive:(NSError *)error {
    NSLog(@"Conversation error: %@", error.localizedDescription);
}
- (void)conversation:(NXMConversation *)conversation didReceiveMessageEvent:(NXMMessageEvent *)event {
    NSLog(@"Received image event: %@", event.imageUrl);
}

Download images from Vonage

Web client

To download an image you need you use the fetch image method.

Mobile client (Android, iOS)

To download an image you need to add JWT to the image retrieval request. The JWT is passed as an Authorization header (Authorization: Bearer <JWT> format). This is the JWT that was used to log in the user.

Various image libraries are handling request headers differently, so below you will full example for the most popular libraries. Notice the JWT being set as the Authorization header for the request:

// ==== LOAD IMAGE USING COIL ==== 
// https://github.com/coil-kt/coil
private fun loadImageUsingCoil(url: String, jwt: String, context: Context) {
    imageView.load(
        Uri.parse(url),
        context.imageLoader,
    ) {
        addHeader("Authorization", "bearer $jwt")
    }
}

// ==== LOAD IMAGE USING GLIDE ==== 
// https://github.com/bumptech/glide
private fun loadImageUsingGlide(url: String, jwt: String, context: Context) {
    val build = LazyHeaders.Builder()
        .addHeader("Authorization", "bearer $jwt")
        .build()

    val glideUrl = GlideUrl(url, build)

    Glide.with(context)
        .load(glideUrl)
        .into(imageView)
}

// ==== LOAD IMAGE USING PICASSO ====
// https://github.com/square/picasso

// Define custom Authentication interceptor
class AuthenticationInterceptor(private val jwt: String) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response = chain.request().let {
        val newRequest = it.newBuilder()
            .header("Authorization", "bearer $jwt")
            .build()

        chain.proceed(newRequest)
    }
}

// Create Picasso instance that uses the Authenticator
private fun getPicassoInstance(jwt: String): Picasso {
    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(AuthenticationInterceptor(jwt))
        .build()

    return Picasso.Builder(requireContext()).downloader(OkHttp3Downloader(okHttpClient)).build()
}

// Load image using custom picasso instance (that under the hood uses the authentication interceptor)
private fun loadImageUsingPicasso(url: String, jwt: String, context: Context) {
    getPicassoInstance(jwt)
        .load(url)
        .into(imageView)
}
// ==== LOAD IMAGE USING GLIDE ==== 
// https://github.com/bumptech/glide
private void loadImageUsingGlide(String url, String jwt, Context context) {
    LazyHeaders build = new LazyHeaders.Builder()
            .addHeader("Authorization", "bearer " + jwt)
            .build();

    GlideUrl glideUrl = new GlideUrl(url, build);

    Glide.with(context)
            .load(glideUrl)
            .diskCacheStrategy(DiskCacheStrategy.NONE)
            .into(imageView);
}

// ==== LOAD IMAGE USING PICASSO ====
// https://github.com/square/picasso

// Define custom Authentication interceptor
class AuthenticationInterceptor implements Interceptor {
    private String jwt;
    public AuthenticationInterceptor(String jwt) {
        this.jwt = jwt;
    }
    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();

        Request newRequest = request.newBuilder()
                    .header("Authorization", "bearer " + jwt)
                    .build();

        return chain.proceed(newRequest);
    }
}

// Create Picasso instance that uses the Authenticator
private Picasso getPicassoInstance(String jwt) {
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(new AuthenticationInterceptor(jwt))
            .build();

    return new Picasso.Builder(requireContext())
            .downloader(new OkHttp3Downloader(okHttpClient))
            .build();
}

// Load image using custom picasso instance (that under the hood uses the authentication interceptor)
private void loadImageUsingPicasso(String url, String jwt, Context context) {
    getPicassoInstance(jwt)
            .load(url)
            .into(imageView);
}

You can download the image using URLSession:

func loadImage(urlString: String, token: String, completionHandler: @escaping (UIImage?) -> Void) {
    if let url = URL(string: urlString) {
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data,
                  let httpResponse = response as? HTTPURLResponse,
                    (200...299).contains(httpResponse.statusCode),
                  error == nil else {
                completionHandler(nil)
                return
            }

            completionHandler(UIImage(data: data))
        }

        task.resume()
    }
}

When calling the above function make sure to update your UIImageView on the main thread:

loadImage(urlString: "IMAGE_URL", token: "JWT") { image in
    if let image = image {
        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }
}

You can download the image using URLSession:

- (void)loadImageWithURLString:(NSString *)urlString
                         token:(NSString *)token
             completionHandler:(void (^_Nonnull)(UIImage * _Nullable image))completionHandler {
    NSURL *url = [[NSURL alloc] initWithString:urlString];

    if (url) {
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
        [request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
        NSURLSessionTask *task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (!error && data && response) {
                NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                if (httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299) {
                    completionHandler([[UIImage alloc] initWithData:data]);
                }
            }
            completionHandler(nil);
        }];

        [task resume];
    }
}

When calling the above function make sure to update your UIImageView on the main thread:

[self loadImageWithURLString:@"IMAGE_URL" token:@"JWT" completionHandler:^(UIImage * _Nullable image) {
    if (image) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.imageView setImage:image];
        });
    }
}];

Reference