Find the Best Demos and Examples

Check out this quick tour to find the best demos and examples for you, and to see how the Felgo SDK can help you to develop your next app or game!

Forums

OverviewFelgo 2 Support (Qt 5) › Firebase storage issue when trying to view an image from storage bucket.

Tagged: ,

Viewing 11 posts - 1 through 11 (of 11 total)
  • Author
    Posts
  • #22556

    Eric

    Hello,

    I’m working on getting Firebase Storage to work. If I use your example code for uploading and displaying a photo taken with the CameraPicker, everything uploads fine but I get the following error when the app tries to retrieve the image and display it (everything in <> are actual values I set as <>).

    QML AppImage: Error transferring https://firebasestorage.googleapis.com/v0/b/<app_id&gt;.appspot.com/o/users/<user_id>/test-image1575638873178.png?alt=media&token=<token> – server replied: Bad Request

    I can log into my online Firebase console and see the the image in the correct location.

    Here’s my code:

    AppButton {
        text: "Capture image + upload"
        onClicked: nativeUtils.displayCameraPicker()
    }
    
    AppText {
        id: status
        text: "Idle"
    }
    
    // this will display the image after it's uploaded
    AppImage {
        id: img
        width: parent.width
        fillMode: AppImage.PreserveAspectFit
        autoTransform: true
    }
    Connections {
        target: nativeUtils
        onCameraPickerFinished: {
            if(accepted) {
                var remote_path = "users/" + dataModel.user_id + "/test-image" + Date.now() + ".png"
                fbStorage.uploadFile(path, remote_path, function(progress, finished, success, downloadUrl) {
                    if(!finished) {
                        status.text = "Uploading... " + progress.toFixed(2) + "%"
                    } else if(success) {
                        img.source = downloadUrl
                        status.text = "Upload completed."
                    } else {
                        status.text = "Upload failed."
                    }
                })
            }
        }
    }
    
    FirebaseStorage {
        id: fbStorage
        config: FirebaseConfig {
            id: fbConfig
    
            projectId: "<app_id>"
            databaseUrl: "https://<app_id>.firebaseio.com"
            storageBucket: "<app_id>.appspot.com"
    
            apiKey: Qt.platform.os === "android"
                ? "<android_key>"
                : "<ios_key>"
    
            applicationId: Qt.platform.os === "android"
                ? "<android_id>"
                : "<ios_id>"
        }
    }

    I can’t find anything online about this issue but it seems to be related to user authentication as if I set the Firebase storage rules to be public, the photo displays as it should, but once I change it to require user authentication, I get the error. My Firebase storage bucket rules are:

    service firebase.storage {
      match /b/{bucket}/o {
        match /users/{userId}/{allPaths=**} {
          allow read, write: if request.auth.uid == userId;
        }
      }
    }

    Again, everything writes to the bucket correctly, it just won’t download the image.

    Any help would be appreciated.

    Thank you,
    Eric

    #22564

    David Bacelj

    Hi, I have also the exact same problem.

     

    Upload works fine but once i try to download I get “Bad request”

    I recognized that when i posted the downlaod url (from uploadFile() ) in the browser the image shows up correctly, but once i fetch it with a felgo component the error shows up with a slightly different URL. when i post the different URL shown in the error code in the browser, the error is the same.

    It feels somehow the felgo component change the URL slightly and therefore cannot fetch it.

     

    Any ideas how to fix?

    /David

    #22604

    Alex
    Felgo Team

    Hi David,

    can you send both mentioned URLs so we can compare them, together with a description of the issue, and maybe a minimal runnable example project to support@felgo.com ?
    @Eric, could this also be the issue for you? Please check the URLs like David mentioned and let us know.

    Cheers,
    Alex

    #22647

    David Bacelj

    Hi Alex, Here are the URLs

     

    Note that token is changed.

    This url is returned from upploadFile() and works fine

    https://firebasestorage.googleapis.com/v0/b/%5BEXAMPLE%5D-875a2.appspot.com/o/users%2Fsecret%2FprofilePic.png?alt=media&token=%5BTOKEN%5D

     

    When I set “img.source = downloadUrl” I get “Bad request” and the url in a slightly different string. note %2Fsecret%2F.
    https://firebasestorage.googleapis.com/v0/b/%5BEXAMPLE%5D-875a2.appspot.com/o/users/secret/profilePic.png?alt=media&token=%5BTOKEN%5D

     

     

     

    
      function uploadImage(path) {
          var uploadFileUrl = Qt.resolvedUrl(path) //image in same folder as this QML file
          var remoteFilePath = "users/secret/profilePic.png"
    
          uploadTask = storage.uploadFile(uploadFileUrl, remoteFilePath,
            function (progress, finished, success, downloadUrl) {
              if(!finished) {
                console.log("Upload progress:", progress, "%")
              } else if(success) {
                img.source = downloadUrl
                console.log("Upload finished! Download URL:", downloadUrl)
              } else {
                console.log("Upload failed!")
              }
            }
          )
      }

    I have also sent a example to support as you requested

     

    Mary Christmas

    /David

    #22735

    Eric

    Hello David,

    Did you get a fix for this issue, by chance?

    Thanks,
    Eric

    #22744

    Alex
    Felgo Team

    Hi Guys,

    this seems to be related to an issue with the QUrl type used for the source property of images, which does autmonatic encoding an messes up the URL returned from Firebase after uploading files to a non-root path. We’re trying to find a fix for this and are also in contact with Qt regarding this issue.

    I hope to have an update for you guys soon.

    Btw: Uploading and displaying images from the root path is working (like in this example: https://felgo.com/doc/felgo-firebasestorage/#example-usage), so if your use-case would allow it, you could store the images in the root path instead.

    Best,
    Alex

    #22767

    David

    Same issue for me. I will save images in root path until the fix.

    #22778

    David

    Hello David,

     

    I have a workaround with the QNetworkAccessManager. If you are interested, I can help you.

    Greetings,

     

    David

    #22779

    David Bacelj

    Awesome!

     

    how did you solve it?

     

    Best regards

    David

    #22780

    David

    To be honest, compared to the effort of giving the picture just a url, it’s a little bit more. But it is a workaround.

    You can download the image, save it in the memory. And give the local url to an image object.

    There are different ways. One way, for example, is to create a C++ object (Downloader) and register it in Qml.

    But first create, for instance, an RequestHandler (I have an interface and some other objects but, to make it a little bit easier I changed the code) HttpsRequestHandler.h:

    class HttpsRequestHandler : public QObject, public IRequestHandler
    {
        Q_OBJECT
        Q_INTERFACES(IRequestHandler)
    public:
        explicit HttpsRequestHandler(QObject *parent = nullptr);
    
        void request(QString url, QVariantMap params) override;
        void reqeust(QString url) override;
    
    signals:
        void requestFinished(QByteArray result);
        void finished();
        void downloadProgessChanged(int percent);
    
    public slots:
        void onRequestFinished(QNetworkReply *reply);
        void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    };

    HttpsRequestHandler.cpp:

     

    ..........
    
    void HttpsRequestHandler::request(QString url, QVariantMap params)
    {
        QEventLoop eventLoop;
    
        QNetworkAccessManager manager;
        QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)),
                         this, SLOT(onRequestFinished(QNetworkReply*)));
        QObject::connect(this, SIGNAL(finished()), &eventLoop, SLOT(quit()));
    
        QUrlQuery query;
    
        if (params.count() > 1) {
            url += "?";
        }
        QUrl networkUrl(url);
    
        foreach (auto key, params.keys()) {
            query.addQueryItem(key, params.value(key).toString());
        }
    
        networkUrl.setQuery(query.query());
    
        QNetworkRequest request(networkUrl);
    
        QNetworkReply *reply = manager.get(request);
    
        connect(reply, &QNetworkReply::downloadProgress, this,
                &HttpsRequestHandler::onDownloadProgress);
        eventLoop.exec(); //blocks stack until "finished()" has been called
    }
    
    void HttpsRequestHandler::onRequestFinished(QNetworkReply *reply)
    {
        emit requestFinished(reply->readAll()); // send result to connected slot
        emit finished(); // end the eventloop
    }
    
    void HttpsRequestHandler::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
    {
        double percent = (100.0 / bytesTotal) * bytesReceived;
        emit downloadProgessChanged(static_cast<int>(percent));
    }
    
    ..........
    

     

    Downloader.h:

    class Downloader : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString file READ file NOTIFY fileChanged)
        Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
    public:
        explicit Downloader(QObject *parent = nullptr);
    
        QString file() const;
    
        bool loading() const;
        void setLoading(bool loading);
    
    signals:
        void fileChanged();
        void loadingChanged();
        void progessChanged(int percent);
    
    public slots:
        Q_INVOKABLE void downloadFile(QString url, QString fileName);
        Q_INVOKABLE void remove();
    
    private:
        HttpsRequestHandler *mRequestHandler = nullptr;
        QString mFile  = "";
        bool mLoading = false;
    };

    Downloader.cpp:

    ..........
    
    
    Downloader::Downloader(QObject *parent) : QObject(parent)
    {
        mRequestHandler = new HttpsRequestHandler(this);
        connect(mRequestHandler, SIGNAL(requestFinished(QByteArray)),
                this, SLOT(onRequestFinished(QByteArray)));
        connect(mRequestHandler, &HttpsRequestHandler::downloadProgessChanged,
                this, &Downloader::progessChanged);
    }
    
    void Downloader::downloadFile(QString url, QString fileName)
    {
        setLoading(true);
        if (mRequestHandler == nullptr) {
            setLoading(false);
            return;
        }
    
        QVariantMap params;
        QStringList list = url.split("?");
        QString sepUrl = list.value(0);
        QStringList paramList = list.value(1).split("&");
    
        foreach (auto param, paramList) {
            QStringList paramParts = param.split("=");
            params.insert(paramParts.value(0), paramParts.value(1));
        }
    
        mRequestHandler->request(sepUrl, params);
    }
    
    void Downloader::onRequestFinished(QByteArray result)
    {
        //save result in a file on the memory with the matching ending (.pdf/ .png/ .jpg)
        // and return the file path
        // I use a singelton FileManager with a part of this code
    //    QString filePath = mDataLocation + "/" + fileName;
    //    QFile myFile(filePath);
    //    if (myFile.open(QIODevice::WriteOnly)) {
    //        myFile.write(result);
    //        myFile.close();
    //        return filePath;
    //    } else {
    //        qDebug() << "File open failed.";
    //        return "";
    //    }
    
        mFile = filePath;
        emit fileChanged();
        setLoading(false);
    
    }
    
    ........

     

     

     

    Register in Qml:

    qmlRegisterType<Downloader>("Downloader", 1, 0, "Downloader");

    Use in Qml:

    import Downloader 1.0
    
    Component.onDestruction: {
         downloader.removeFile() //if you don't need it anymore
    }
    
    Component.onCompleted: {
        //take the https url from the firebase storage and the downloader should handle it
        downloader.downloadFile("https url")
    }
    
    Downloader {
        id: downloader
    
        onFileChanged: {
              //here you can use the  local file url
        }
        onProgessChanged: {
              progressTxt.text = qsTr("Loading...") + " " + percent + " %"
        }
    }

     

    As I said, it is a little bit more. And the key here is to split the https url in url and params. The QNetworReqeustManager can handle it now. Download the data, save the Data (ByteArray) in a file and give the local file path to the image. It is fun to program it and handle the qt framework.

     

    Greetings,

     

    David

    #22801

    Alex
    Felgo Team

    Hi Guys,

    I also prepared a minimal workaround using a bit of C++. It’s only a small helper class to create a correctly formatted URL type from the encoded path returned from Firebase, which can be used directly in your QML code then.

    Here is the related thread: https://felgo.com/developers/forums/t/using-downloadableressource-with-user-protected-firebase-storage#post-22800

    We’ll have a look if we can provide this with the engine or return the path already as correct URL type, so the automatic conversion issue is resolved.

    Cheers,
    Alex

Viewing 11 posts - 1 through 11 (of 11 total)

RSS feed for this thread

You must be logged in to reply to this topic.