Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. How to emit signal to QML from Java callback executed from QtActivity class started via external intents?
QtWS25 Last Chance

How to emit signal to QML from Java callback executed from QtActivity class started via external intents?

Scheduled Pinned Locked Moved Unsolved Mobile and Embedded
2 Posts 1 Posters 904 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    J Offline
    john_smitty
    wrote on last edited by john_smitty
    #1

    I have been searching for an answer to this for 2 days and at last I have resorted to asking on the forums. If this is already answered somewhere, I apologize for not being able to find it.

    I want my Qt app to be able to receive intents from other Android apps and send them to QML via a signal. Right now I only want to test this with simple text/plain intents. I want a user to select some text on Android, click share, select my app, then when my app opens it displays that text somewhere. So far I have learned that to register Java natives in a Java class that inherits QtActivity, you must do it from the main.cpp file otherwise it crashes.
    I am programming this from Linux using Qt 5.15.
    Here is my main.cpp file

    //main.cpp
    #include <QApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "backend.h"
    
    #ifdef Q_OS_ANDROID
    //Register special Java callback functions immediately once the app loads because they must be executed if the app is started via external Android intents
    
    static void shareTextToQML(JNIEnv *env, jobject thiz, jstring text)
    {
        //This works
        qDebug() << env->GetStringUTFChars(text, nullptr);
    
        //This is not received in QML
        emit BackEnd::instance()->testSignal(env->GetStringUTFChars(text, nullptr));
    }
    
    static JNINativeMethod methods[] = {
        { "javaShareTextToQML", // const char* function name;
            "(Ljava/lang/String;)V", // const char* function signature
            (void *)shareTextToQML // function pointer
        }
    };
    
    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
    {
        JNIEnv* env;
        // get the JNIEnv pointer.
        if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
            return JNI_ERR;
    
        // step 3
        // search for Java class which declares the native methods
        jclass javaClass = env->FindClass("org/sien/qfandid/ShareActivity");
        if (!javaClass)
            return JNI_ERR;
    
        // step 4
        // register our native methods
        if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0)
            return JNI_ERR;
    
        return JNI_VERSION_1_6;
    }
    #endif
    
    int main(int argc, char *argv[])
    {
        QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        //This is just a shared enumeration between C++ and QML. It's not related to my problem
        BackEnd::registerRequestTypeInQML();
    
        BackEnd *backend = new BackEnd(&app);
        engine.rootContext()->setContextProperty(QLatin1String("rootBackend"), backend);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    

    And here is my java file that receives outside intents

    //ShareActivity.java
    package org.sien.qfandid;
    
    import org.qtproject.qt5.android.bindings.QtActivity;
    import android.content.Intent;
    import java.io.File;
    import android.net.Uri;
    import android.os.Bundle;
    
    public class ShareActivity extends QtActivity
    {
        public ShareActivity() {}
    
        private static native void javaShareTextToQML(String text);
    
        public static boolean intentPending;
        public static boolean intentInitialized;
    
        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public void onNewIntent(Intent intent)
        {
            super.onNewIntent(intent);
    
            processIntent(intent);
        }
    
        private void processIntent(Intent intent)
        {
            Uri uri;
            String scheme;
            String action = intent.getAction();
            String type = intent.getType();
    
            if (Intent.ACTION_SEND.equals(action) && type != null)
            {
                if ("text/plain".equals(type))
                {
                    String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
                    if (sharedText != null)
                    
                        //This is where I call the C++ function
                        javaShareTextToQML(sharedText);
                }
            }
        }
    }
    

    Now this is part of the main.qml file where I have a connections object and I want to output the shared text from here, but it is never received.

    //main.qml
        Connections {
            target: rootBackend
            function onTestSignal(message)
            {
                //globalBackend.makeNotification("Test", message)
                console.debug("received signal")
                console.debug(message)
            }
        }
    

    And now here is how I've implemented the instance() method I use in the main.cpp file:
    Relevant parts from the backend.h file:

    //backend.h
    class BackEnd : public QObject
    {
        Q_OBJECT
    
        QML_ELEMENT
    
    public:
        explicit BackEnd(QObject *parent = nullptr);
    
        //For getting the BackEnd instance in order to access and send signals from static methods
        //E.g. native C++ methods called from Java must be static
        static BackEnd *m_instance;
    
        static BackEnd *instance()
        {
            return m_instance;
        }
    
    //etc etc
    
    signals:
        void testSignal(QString message);
    

    Instance related parts from the backend.cpp file:

    //backend.cpp
    #include "backend.h"
    
    //etc etc
    
    BackEnd *BackEnd::m_instance = nullptr;
    
    BackEnd::BackEnd(QObject *parent) : QObject(parent)
    {
        m_instance = this;
    }
    

    I'm not sure if I need to share my android manifest too, but here are some relevant parts from it

    //Android manifest
    <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.sien.qfandid.ShareActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleInstance" android:taskAffinity="">
    
    //etc etc
    
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    

    I believe everything is in place as it should be. I tried to cut out as much irrelevant code as I could, but I still ended up pasting quite a lot so I apologize.
    The current state so far is: when I launch the app, move out of it, select some text, click share, then select my app it reaches as far as the qDebug() << env->GetStringUTFChars(text, nullptr); part in the main.cpp. However, when I try to execute emit BackEnd::instance()->testSignal(env->GetStringUTFChars(text, nullptr)); afterwards, the signal is not received in the little Connections object defined above in main.qml.

    From the tests I've performed so far I assume the signal doesn't reach QML because BackEnd::instance() doesn't actually return the instance where the QML interface "lives", though I may be wrong. Could anyone help me? I don't know what else to try. I've spent almost 2 days reading blogs, forums and the documentation, but there aren't many examples of what I'm trying to do. The closest I could find was this old blog post https://www.qt.io/blog/2018/01/16/sharing-files-android-ios-qt-app-part-2
    But the blog post itself doesn't show all the necessary information and the code in the github page was too complicated for me to understand how to do exactly what I want to do.

    To reiterate, all that's left to do is figure out how to send the string message to QML from the main.cpp function static void shareTextToQML(JNIEnv *env, jobject thiz, jstring text). However, I assume there's a discrepancy between the instance returned from BackEnd::instance() and the instance where the QML interface resides, but I don't know how to resolve it. Any help would be much appreciated.

    1 Reply Last reply
    0
    • J Offline
      J Offline
      john_smitty
      wrote on last edited by
      #2

      I believe I just solved it after sleeping on it lol. I was so close I don't know how I didn't see this before.

      I followed the assumption that there's an instance discrepancy and I thought that it was caused by the fact that m_instance , a static variable, was changed every time a new BackEnd class was created (which happens very often in my app), because m_instance = this was executed in the BackEnd constructor. So I did some code reorganization:

      In the header file I moved static BackEnd *m_instance; to become a private variable and declared this public class method Q_INVOKABLE void storeQmlInstance();
      In the cpp file I removed this line m_instance = this; from the constructor, so now it looks like this

      BackEnd::BackEnd(QObject *parent) : QObject(parent)
      {
      
      }
      

      Then I implemented the method like this:

      void BackEnd::storeQmlInstance()
      {
          m_instance = this;
      }
      

      Now, most importantly, in the main.qml file I added this line Component.onCompleted: globalBackend.storeQmlInstance();. (globalBackend is the ID of the BackEnd instance defined in the main.qml)
      Now remember that the method instance() is still a public static method which simply returns m_instance.
      So now, in my main.cpp when this is executed

      static void shareTextToQML(JNIEnv *env, jobject thiz, jstring text)
      {
          emit BackEnd::instance()->testSignal(env->GetStringUTFChars(text, nullptr));
      }
      

      It is finally received in the main.qml Connections object, whose target this time is globalBackend because that's where storeQmlInstance() is called from.

      1 Reply Last reply
      0

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved