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)
conversation.sendAttachment(imageFile, object : NexmoRequestListener<Void> {
    override fun onSuccess(p0: Void?) {
        Log.d("TAG", "Image sent")
    }

    override fun onError(apiError: NexmoApiError) {
        Log.d("TAG", "Error: Image not sent ${apiError.message}")
    }
})
conversation.sendAttachment(imageFile, new NexmoRequestListener<Void>() {
    public void onSuccess(@Nullable Void p0) {
        Log.d("TAG", "Image sent");
    }

    public void onError(@NotNull NexmoApiError apiError) {
        Log.d("TAG", "Error: Image not sent " + apiError.getMessage());
    }
});
let image = UIImage(named: "file.png")
guard let imageData = image?.pngData() else { return }

conversation.sendAttachment(with: NXMAttachmentType.image,
                            name: "File name",
                            data: imageData,
                            completionHandler: { (error) in
    if let error = error {
        NSLog("Error sending image: \(error.localizedDescription)")
        return
    }
    NSLog("Image sent")
})
[conversation sendAttachmentWithType:NXMAttachmentTypeImage 
                                name:@"File name"
                                data:[NSData dataWithContentsOfFile:@"file.png"]
                   completionHandler:^(NSError * _Nullable error) {
    NSLog(@"Image sent");
}];

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 onAttachmentEvent(attachmentEvent: NexmoAttachmentEvent) {
        val userName = attachmentEvent.getEmbeddedInfo.user.name

        Log.d("TAG", "Image event received. User $userName")

        // Event contains URL's for multiple sizes of images
        attachmentEvent.original.url
        attachmentEvent.medium.url
        attachmentEvent.thumbnail.url

        //Download the image using one of open-source libraries: Coil, Picasso, Glide, etc.
    }

    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 onAttachmentEvent(@NonNull NexmoAttachmentEvent attachmentEvent) {
        String userName = attachmentEvent.getEmbeddedInfo().getUser().getName();

        Log.d("TAG", "Image event received. User " +  userName);

        // Event contains URL's for multiple sizes of images
        attachmentEvent.getOriginal().getUrl();
        attachmentEvent.getMedium().getUrl();
        attachmentEvent.getThumbnail().getUrl();

        //Download the image using one of open-source libraries: Coil, Picasso, Glide, etc.
    }

    @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: NXMImageEvent):

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: NXMImageEvent) {
        NSLog("Received image event: \(event.originalImage)")
    }
}

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

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 didReceiveImageEvent:(NXMImageEvent *)event {
    NSLog(@"Received image event: %@", event.originalImage);
}

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