Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃悰 Bug Report: React Native File Upload #27

Closed
2 tasks done
rohankm opened this issue Jul 3, 2022 · 12 comments
Closed
2 tasks done

馃悰 Bug Report: React Native File Upload #27

rohankm opened this issue Jul 3, 2022 · 12 comments
Assignees
Labels
bug Something isn't working

Comments

@rohankm
Copy link

rohankm commented Jul 3, 2022

馃憻 Reproduction steps

Im trying to upload files from react native expo

steps to reproduce

yarn create expo-app demo
cd demo
yarn add appwrite

here is my App.js

import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";

import { Client, Account, Storage } from "appwrite";
const client = new Client();
client
.setEndpoint("https://demo.com/v1") // We set the endpoint, change this if your using another endpoint URL.
.setProject("62aa497432281eaabc7a"); // Your project ID

const account = new Account(client);
const storage = new Storage(client);

export default function App() {
const login = () => {
  const promise = account.createEmailSession("demo@demo.com", "demodemo");

  promise.then(
    function (response) {
      console.log(response); // Success
    },
    function (error) {
      console.log(error); // Failure
    }
  );
};
const upload = () => {
  const file = new File(["HELOOOO"], "filenamenew.txt", {
    type: "text/plain",
  });
  const promise2 = storage.createFile("userImages", "unique()", file);

  promise2.then(
    function (response) {
      console.log(response); // Success
    },
    function (error) {
      console.log(error); // Failure
    }
  );
};
return (
<View style={styles.container}>
    <Button onPress={login} title="login" />
    <Button onPress={upload} title="upload data" />
  </View>

);
}

const styles = StyleSheet.create({
container: {
  flex: 1,
  backgroundColor: "#fff",
  alignItems: "center",
  justifyContent: "center",
},
});

馃憤 Expected behavior

It should upload a file named filenamenew.txt to the specified bucket. The same works properly on react native web. but its not working in with android and ios

馃憥 Actual Behavior

Network request failed
at node_modules@babel\runtime\helpers\construct.js:19:9 in _construct
at node_modules@babel\runtime\helpers\wrapNativeSuper.js:26:22 in Wrapper
at http://192.168.0.108:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false&strict=false&minify=false:120669:293 in _createSuperInternal
at node_modules\appwrite\dist\cjs\sdk.js:72:8 in AppwriteException#constructor
at node_modules\appwrite\dist\cjs\sdk.js:391:22 in __awaiter$argument_3
at node_modules@babel\runtime\helpers\regeneratorRuntime.js:86:13 in tryCatch
at node_modules@babel\runtime\helpers\regeneratorRuntime.js:66:31 in
at node_modules\appwrite\dist\cjs\sdk.js:25:46 in rejected
at node_modules\promise\setimmediate\core.js:37:13 in tryCallOne
at node_modules\promise\setimmediate\core.js:123:24 in setImmediate$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:248:12 in _allocateCallback$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:112:14 in _callTimer
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:162:14 in _callReactNativeMicrotasksPass
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:413:41 in callReactNativeMicrotasks
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:391:6 in __callReactNativeMicrotasks
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:133:6 in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:368:10 in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:132:4 in flushedQueue

馃幉 Appwrite version

Version 0.10.x

馃捇 Operating system

Windows

馃П Your Environment

No response

馃憖 Have you spent some time to check if this issue has been raised before?

  • I checked and didn't find similar issue

馃彚 Have you read the Code of Conduct?

@rohankm rohankm added the bug Something isn't working label Jul 3, 2022
@stnguyen90
Copy link
Contributor

Thanks for reporting this! We're still trying to find a resource to replicate this problem and troubleshoot further. We'll report back when we have more info.

@rohankm
Copy link
Author

rohankm commented Jul 9, 2022

@stnguyen90 here is the manual code

it has

  1. Login
    2.Sdk upload Data
  2. xhr methods to upload data
  3. axios to upload data

on ios it just creates a empty document with the file name
on android it just throws network error

note : need to change the api url

import { Button, StyleSheet, Text, View } from "react-native";
import React from "react";
import { Client, Account, Storage } from "appwrite";
import axios from "axios";
const client = new Client();

client
  .setEndpoint("https://demo.in/v1") // We set the endpoint, change this if your using another endpoint URL.
  .setProject("62aa497432281eaabc7a"); // Your project ID

const account = new Account(client);
const storage = new Storage(client);

export default function App() {
  const login = () => {
    const promise = account.createEmailSession("demo@demo.com", "demodemo");

    promise.then(
      function (response) {
        console.log(response); // Success
      },
      function (error) {
        console.log(error); // Failure
      }
    );
  };

  const xhrupload = () => {
    console.log("_--------------------------------------_");
    const file = new File(["HELOOOO"], "finnw.txt", {
      type: "text/plain",
    });
    let formData = new FormData();
    formData.append("fileId", "unique()");
    formData.append("file", file);
    console.log("formData", formData);
    const sendData = sendXmlHttpRequest(formData).then(
      function (response) {
        console.log("response", response); // Success
      },
      function (error) {
        console.log("error", error); // Failure
      }
    );
  };
  const sdkupload = () => {
    console.log("_--------------------------------------_");
    const file = new File(["HELOOOO"], "fi.txt", {
      type: "text/plain",
    });

    const promise2 = storage.createFile("userImages", "unique()", file);
    promise2.then(
      function (response) {
        console.log(response); // Success
      },
      function (error) {
        console.log(error); // Failure
      }
    );
    return;
  };
  function sendXmlHttpRequest(data) {
    const xhr = new XMLHttpRequest();

    return new Promise((resolve, reject) => {
      xhr.onreadystatechange = (e) => {
        if (xhr.readyState !== 4) {
          return;
        }
        console.log("xhr.status", xhr);

        if (xhr.status === 201) {
          resolve(JSON.parse(xhr.response));
        } else {
          reject("Request Failed");
        }
      };

      xhr.open(
        "POST",
        "https://demo.in/v1/storage/buckets/userImages/files/"
      );
      xhr.withCredentials = true;
      // xhr.setRequestHeader("content-type", "multipart/form-data");
      xhr.setRequestHeader("X-Appwrite-Project", "62aa497432281eaabc7a");
      xhr.setRequestHeader("X-Appwrite-Response-Format", "0.15.0");
      xhr.setRequestHeader("x-sdk-version", "appwrite:web:9.0.1");
      xhr.send(data);
    });
  }
  const axiosUpload = () => {
    console.log("_--------------------------------------_");
    const file = new File(["HELOOOO"], "fi.txt", {
      type: "text/plain",
    });
    let formData = new FormData();
    formData.append("fileId", "unique()");
    formData.append("file", file);
    console.log("formData", formData);
    axios({
      url: "https://demo.in/v1/storage/buckets/userImages/files/",
      method: "POST",
      data: formData,
      headers: {
        "X-Appwrite-Project": "62aa497432281eaabc7a",
        "X-Appwrite-Response-Format": "0.15.0",
        "x-sdk-version": "appwrite:web:9.0.1",
      },
    })
      .then(function (response) {
        console.log("response :", response);
      })
      .catch(function (error) {
        console.log("error from image :", error);
      });
  };
  return (
    <View style={styles.container}>
      <Button onPress={login} title="login" />
      <Button onPress={sdkupload} title="sdk upload data" />
      <Button onPress={xhrupload} title="xhrupload upload data" />
      <Button onPress={axiosUpload} title="axiosUpload" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

@rohankm
Copy link
Author

rohankm commented Jul 9, 2022

Finally I was able to solve the issue, thanks to @stnguyen90 for helping out

the issue was with the form data handled by the sdk.

here is my code and i have used custom form and XMLHttpRequest() to upload the images

import { Button, Image, StyleSheet, Text, View } from "react-native";
import React, { useState } from "react";
import * as ImagePicker from "expo-image-picker";
import { Client, Account } from "appwrite";
import axios from "axios";
const client = new Client();
const API_URL = "https://demo.in/v1";
const PROJECT_ID = "62aa497432281eaabc7a";
const BUCKET_ID = "userImages";
client
  .setEndpoint(API_URL) // We set the endpoint, change this if your using another endpoint URL.
  .setProject(PROJECT_ID); // Your project ID

const account = new Account(client);

export default function App() {
  const [image, setImage] = useState(null);
  const [succ, setSucc] = useState(false);
  const login = () => {
    const promise = account.createEmailSession("demo@demo.com", "demodemo");

    promise.then(
      function (response) {
        console.log(response); // Success
      },
      function (error) {
        console.log(error); // Failure
      }
    );
  };

  function sendXmlHttpRequest(data) {
    const xhr = new XMLHttpRequest();

    return new Promise((resolve, reject) => {
      xhr.onreadystatechange = (e) => {
        if (xhr.readyState !== 4) {
          return;
        }
        console.log("xhr.status", xhr);

        if (xhr.status === 201) {
          resolve(JSON.parse(xhr.response));
        } else {
          reject("Request Failed");
        }
      };

      xhr.open("POST", `${API_URL}/v1/storage/buckets/${BUCKET_ID}/files/`);
      xhr.withCredentials = true;
      // xhr.setRequestHeader("content-type", "multipart/form-data");
      xhr.setRequestHeader("X-Appwrite-Project", PROJECT_ID);
      xhr.setRequestHeader("X-Appwrite-Response-Format", "0.15.0");
      xhr.setRequestHeader("x-sdk-version", "appwrite:web:9.0.1");
      xhr.send(data);
    });
  }

  const pickImage = async () => {
    // No permissions request is necessary for launching the image library
    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.All,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    console.log(result);

    if (!result.cancelled) {
      setImage(result.uri);
    }
  };

  const uploadImage = async () => {
    let filename = image.split("/").pop();

    // Infer the type of the image
    let match = /\.(\w+)$/.exec(image);
    let type = match ? `image/${match[1]}` : `image`;

    console.log("_--------------------------------------_");
    let formData = new FormData();
    formData.append("fileId", "unique()");
    formData.append("file", {
      uri: image,
      name: filename,
      type,
    });
    // formData.append("read", "");
    // formData.append("write", "");

    console.log("formData", formData);
    await sendXmlHttpRequest(formData).then(
      function (response) {
        console.log("response", response); // Success
        setSucc(true);
      },
      function (error) {
        console.log("error", error); // Failure
      }
    );
  };

  return (
    <View style={styles.container}>
      <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
        <Button onPress={login} title="login" />
        <Button title="Pick an image from camera roll" onPress={pickImage} />
      </View>
      <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
        {image && (
          <>
            <Image
              source={{ uri: image }}
              style={{ width: 200, height: 200 }}
            />
            <Button onPress={uploadImage} title="uploadImage" />
          </>
        )}
        {succ && <Text style={{ fontSize: 32 }}>UPLOADED</Text>}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

@johnfidel98
Copy link

johnfidel98 commented Feb 3, 2023

this is still an issue... the solution above works - for files less than the chunk size of 5mb... any thing larger than that doesn't work...

I've been looking into the module react-native-chunk-upload uploads chunks in the format *.tmp ... not sure if this is the reason why it doesn't work

@Badbeef1
Copy link

It's still an issue with the SDK but this is a version of the code above using the fetch api instead of building the XMLHttpRequest and it's working good for my case :

fetch(`${API_URL}/v1/storage/buckets/${BUCKET_ID}/files/`, {
        method: "POST",
        headers: {
            "content-type": "multipart/form-data",
            "X-Appwrite-Project": PROJECT_ID,
            "x-sdk-version": "appwrite:web:10.2.0",
        },
        body: formData,
        credentials: "include",
    });

@trashcoder
Copy link

I've been looking into the module react-native-chunk-upload uploads chunks in the format *.tmp ... not sure if this is the reason why it doesn't work

I tried it and also react-native-background-upload. When it comes to a chunked upload, i get a response code of 400, but only with React Native. The same code in a React Website works.

@stnguyen90
Copy link
Contributor

stnguyen90 commented Sep 7, 2023

So, the crux of the problem with our SDK and react-native is FromData + File. In the browser, you can put a File into FormData and the browser will handle the multipart form request fine. React-Native, doesn't handle File the same way which is why you need to make formdata like:

    let formData = new FormData();
    formData.append("fileId", "unique()");
    formData.append("file", {
      uri: image,
      name: filename,
      type,
    });

Fyi, this is what react native expects a file to be: https://github.com/facebook/react-native/blob/17ecae9ce7bded79ab3a083c9d07e15460e5635c/packages/react-native/types/modules/globals.d.ts#L107

Now, we'd like to use the same SDK and for both browser and react-native and we'd like the signature to kind of be the same. I'm not sure what makes sense from the react-native side, though. How else are people getting files besides ImagePicker? And what do you have if not a URI?

@stnguyen90 stnguyen90 self-assigned this Sep 7, 2023
@rohankm
Copy link
Author

rohankm commented Sep 8, 2023

So, the crux of the problem with our SDK and react-native is FromData + File. In the browser, you can put a File into FormData and the browser will handle the multipart form request fine. React-Native, doesn't handle File the same way which is why you need to make formdata like:

    let formData = new FormData();
    formData.append("fileId", "unique()");
    formData.append("file", {
      uri: image,
      name: filename,
      type,
    });

Now, we'd like to use the same SDK and for both browser and react-native and we'd like the signature to kind of be the same. I'm not sure what makes sense from the react-native side, though. How else are people getting files besides ImagePicker? And what do you have if not a URI?

It's just file uri unless you want to convert it to a blob

@rohankm
Copy link
Author

rohankm commented Sep 8, 2023

btw here is my updated code and it works fine for me

const uploadToStorage = async (
  bucketId,
  uri,
  permissions,
  fileId = "unique()",
  name,
  ftype
) => {
  // Infer the type of the image
  const match = /\.(\w+)$/.exec(uri);
  const filename = name ? `${name}.${match[1]}` : uri.split("/").pop();
  const fileIdP = filename.split("_").pop().split(".").shift();
  const type = ftype ? ftype : match ? `image/${match[1]}` : `image`;

  const formData = new FormData();
  formData.append("fileId", fileIdP);
  formData.append("file", {
    uri: uri,
    name: filename,
    type,
  });
  permissions.forEach((p) => {
    formData.append("permissions[]", p);
  });

  const response = await fetch(
    `${appwrite.config.endpoint}/storage/buckets/${bucketId}/files`,
    {
      method: "POST", // or 'PUT'
      headers: {
        ...appwrite.headers,
        "Content-Type": "multipart/form-data;",
      },
      body: formData,
    }
  );

  return response.json();
};

@saja25
Copy link

saja25 commented Feb 20, 2024

hi , this doesn't work for the new web sdk version, any solution on how to upload images ?

@rohankm
Copy link
Author

rohankm commented Feb 20, 2024

I wish I could help but I migrated to supabase

@lohanidamodar
Copy link
Member

@rohankm We now have React Native SDK. Please check it out at https://github.com/appwrite/sdk-for-react-native

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

8 participants