I have a screen where a user is able to upload an image to Supabase (using the built in Upload Media to Supabase action upon tap of an upload button on the screen). As part of this action, I want to create a thumbnail sized version of the image, and save this to Supabase storage as well. I plan to use the thumbnail sized version in most places, but still need the regular sized version I will use in a couple spots where the image is larger on the screen. I have yet to get this to work despite several approaches (see below).
How do I upload the original image and a thumbnail to Supabase storage on Media Upload?
So far I have tried to create a custom action that takes an Image Url, and a width. The function shrinks the image successfully, but I have not been able to successfully upload to Supabase inside the custom action. I am using the Supabase method shown in their docs seen here: https://supabase.com/docs/reference/dart/storage-from-upload where it states that the file parameter can be sent as either "File" or Uint8List.
In the Supabase upload call, I'm not sure how I would use File as the parameter, given that I am resizing a file and want to send this, so I tried sending the file as a Uint8List, and it fails. (see error below). Here's the custom action that attempts both shrink and upload to Supabase:
import 'dart:io';
import 'dart:async';
import 'dart:typed_data';
import 'package:image/image.dart' as img;
import 'package:http/http.dart' as http;
final supabase = SupabaseClient('https://{myProject}.supabase.co',
'{MyServicRoleKey}');
Future<String?> shrinkAndUploadImage(String imageUrl, int width) async {
try {
// Fetch image data from URL
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode != 200) {
print('Failed to load image from URL: $imageUrl');
return null;
}
// Decode image data
Uint8List imageData = response.bodyBytes;
img.Image? image = img.decodeImage(imageData);
if (image == null) {
print('Failed to decode image from loaded data');
return null; // Handle error: Unable to decode image
}
print('Original image size: ${image.length} bytes');
// Resize the image
img.Image resizedImage = img.copyResize(image, width: width);
print('Resized image size: ${resizedImage.length} bytes');
// Encode the resized image to uploadable bytes
List<int> resizedImageBytes = img.encodePng(resizedImage);
print('Image encoded to PNG format');
// Convert resized image bytes to Uint8List (optional)
final Uint8List resizedBytes = Uint8List.fromList(resizedImageBytes);
// Upload the resized image to Supabase Storage
final String resizedUrl = await supabase.storage.from('content').upload(
'gallery/${DateTime.now().millisecondsSinceEpoch}-small.png',
resizedBytes,
fileOptions: const FileOptions(cacheControl: '3600', upsert: false),
);
// Return the URL of the resized image
return resizedUrl;
} catch (e) {
print('Error: $e');
return null;
// show error and return null
}
}
And the error:
I've also tried sending as "File" type (not really sure what that is). In that test, the code looks like this:
// Convert resized image bytes to Uint8List (optional)
final Uint8List resizedBytes = Uint8List.fromList(resizedImageBytes);
// Create a File object
final File resizedFile = File.fromRawPath(resizedBytes);
// Upload the resized image to Supabase Storage
final String resizedUrl = await supabase.storage.from('content').upload(
'gallery/${DateTime.now().millisecondsSinceEpoch}-small.png',
resizedFile,
fileOptions: const FileOptions(cacheControl: '3600', upsert: false),
);
// Return the URL of the resized image
return resizedUrl;
} catch (e) {
print('Error: $e');
return null;
// show error and return null
}
}
This compiles, but causes a build error (build error screenshot is attached). I tried removing dart.io, and then the custom action shows compile errors:
As an alternative, I have also tried just doing the shrink image in the custom action, which returns a FFUploadedFile. I figured I could then invoke a Upload File To Supabase action, but inside the Action Flow Editor, I get errors on each upload step stating that you can't have more multiple upload files in the same node.
Here's my custom action that does just the shrink (no upload to Supabase):
import 'package:http/htt.dart' as http;
import 'package:image/image.dart' as img;
Future<FFUploadedFile?> shrinkImage(String imageUrl, int width) async {
try {
// Fetch image data from URL
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode != 200) {
print('Failed to load image from URL: $imageUrl');
return null;
}
// Decode image data
Uint8List imageData = response.bodyBytes;
// Resize the image
img.Image? image = img.decodeImage(imageData);
if (image == null) {
print('Failed to decode image from loaded data');
return null; // Handle error: Unable to decode image
}
print('Original image size: ${image.length} bytes');
// Resize the image
img.Image resizedImage = img.copyResize(image, width: width);
print('Resized image size: ${resizedImage.length} bytes');
// Encode the resized image to uploadable bytes
List<int> resizedImageBytes = img.encodePng(resizedImage);
print('Image encoded to PNG format');
// Convert resized image bytes to Uint8List (optional)
final Uint8List resizedBytes = Uint8List.fromList(resizedImageBytes);
final FFUploadedFile resizedFile = FFUploadedFile(
name: '${DateTime.now().millisecondsSinceEpoch}-small.png',
bytes: resizedBytes,
);
// Return the URL of the resized image
return resizedFile;
} catch (e) {
print('Error: $e');
return null;
// show error and return null
}
}
And the action flow logic (with errors):