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.