1

I am building an application with Flutter. I am using using a mix of newer technologies that I am struggling to piece together.

My actual issue is that I cannot get images to load in the app when using the following methods:

  1. Insert test data into MongoDB using Mongoengine, images are inserted into GridFS using this method.
  2. Query GraphQL server for data and retrieve data and receive images from GridFS in form of bytes but in a string - e.g "b'/9j/4AAQSkZJRgABAQEASABIAAD/4V0X .... '"
  3. Use that bytes string to load images in app with something like Image.memory()

But the images fail to load with an error: type String is not a subtype of type Uint8List. So I tried to convert the string of bytes from a String to raw bytes by doing:

List<int> bytesList = imageData['image']['data'].codeUnits;
Uint8List thumbImageBytes = Uint8List.fromList(bytesList);

I get the following exception:

I/flutter ( 4303): ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞══════
I/flutter ( 4303): The following _Exception was thrown while resolving an image:
I/flutter ( 4303): Exception: Could not instantiate image codec.

I have no idea what I can do to fix this, I cannot seem to find anything by googling etc. It would seem there is no information available for this exact scenario except for this S.O question which is what I have tried to do. I have also tried all the methods sugested in comments, followed the suggested links and tried all available combinations of answers, comments, everything.

My set up is as follows;

Main app: Flutter/Dart
API Server: Python/Flask/Mongoengine based GraphQL/Graphene API Backend Database: MongoDB

The Python side;

A Mongoengine Document model:

class Product(Document):
    meta = {'collection': 'product'}
    name = StringField(unique=True)
    price = FloatField()
    sale_price = FloatField()
    description = StringField()
    image = FileField()
    thumb = FileField()
    created_at = DateTimeField(default=datetime.utcnow)
    edited_at = DateTimeField()
    # user = ReferenceField(User)

    def __repr__(self):
        return f'<Product Model::name: {self.name}>'

A Graphene schema for the model:

class ProductAttribute:
    name = graphene.String()
    price = graphene.Float()
    sale_price = graphene.Float()
    description = graphene.String()
    image = Upload()
    thumb = graphene.String()
    created_at = graphene.DateTime()
    edited_at = graphene.DateTime()

class Product(MongoengineObjectType):
    """Product node."""

    class Meta:
        model = ProductModel
        interfaces = (graphene.relay.Node,)

class CreateProductInput(graphene.InputObjectType, ProductAttribute):
    """Arguments to create a product."""
    pass

class CreateProduct(graphene.Mutation):
    """Create a product."""
    product = graphene.Field(lambda: Product, description="Product created by this mutation.")

    class Arguments:
        input = CreateProductInput()
        image = Upload(required=True)

    def mutate(self, info, image, input):
        data = utils.input_to_dictionary(input)
        data['created_at'] = datetime.utcnow()
        data['edited_at'] = datetime.utcnow()
        print(data)

        product = ProductModel(**data)
        product.save()

        return CreateProduct(product=product)

My base Graphene schema:

class Query(graphene.ObjectType):
    """Query objects for GraphQL API."""

    node = graphene.relay.Node.Field()
    single_product = graphene.relay.Node.Field(schema_product.Product)
    all_products = MongoengineConnectionField(schema_product.Product)

class Mutations(graphene.ObjectType):
    createProduct = schema_product.CreateProduct.Field()


schema = graphene.Schema(query=Query, types=[schema_product.Product], mutation=Mutations)
Jamie Lindsey
  • 928
  • 14
  • 26
  • Are you sure your images aren't encoded as strings using, say, base64? It seems rather suspicious if the string you get is composed entirely of printable characters. – jamesdlin Sep 05 '19 at 15:18

1 Answers1

0

It's very suspicious that:

  1. Your binary data is stored in a String (this is usually wrong).
  2. Your String happens to be composed entirely of printable characters.

It's therefore likely that you're getting back binary data that's been encoded to a printable string. A common encoding is base64, and sure enough, when I tried to base64-encode a few different types of images, I see that base64-encoding JPEG files generates a string that starts off with /9j/4AAQSk, just like the string you get.

You are definitely getting back base64-encoded data. If you aren't doing the encoding yourself, then something is automatically doing the encoding for you, and likely there's a symmetric mechanism to decode it for you. If not, you'll need to explicitly base64-decode your String to get back binary data. You can use dart:convert to decode it for you.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • Hey thanks, I should have realised that it was in fact base64! So basically the bytes were coming from pythons `open(file, 'rb')` which gives the `b''` bytes representation which is then fine with mongodb, but when graphene hands the data back its a string like `"b''"` so the bytes representations becomes a string. I simply "sliced" the string `String byteString = allProductsItem['thumb']['data']; Uint8List thumbImage = base64.decode(byteString.substring(2, byteString.length - 1));` – Jamie Lindsey Sep 05 '19 at 16:37