Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Calling Java method from within Qt yields exception



  • I am trying to implement this example functionality within my application: https://doc.qt.io/qt-5/qtandroidextras-notification-example.html

    Here is my java class:

    package com.company.mri;
    
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.content.Context;
    
    public class NotificationClient extends org.qtproject.qt5.android.bindings.QtActivity
    {
        private static NotificationManager m_notificationManager;
        private static Notification.Builder m_builder;
        private static NotificationClient m_instance;
    
        public NotificationClient()
        {
            m_instance = this;
        }
    
        public static void notify(String title, String body)
        {
            if (m_notificationManager == null)
            {
                m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE);
                m_builder = new Notification.Builder(m_instance);
                m_builder.setSmallIcon(R.drawable.icon);
                m_builder.setContentTitle(title);
            }
    
            m_builder.setContentText(body);
            m_notificationManager.notify(1, m_builder.build());
        }
    }
    

    This is how I call it from within cpp:

    * @brief   Creates a notification in the Android OS top bar
     * @param   title: Message title text.
     * @param   body: message body.
     * @param   duration_s: this parameter is unused on this OS
     */
    void COsAndroid::showNotification(const QString& title,
                                      const QString& body,
                                      const int duration_s)
    {
        Q_UNUSED(duration_s)
    
        QAndroidJniObject andIniObjTitle = QAndroidJniObject::fromString(title);
        QAndroidJniObject andIniObjBody = QAndroidJniObject::fromString(body);
    
        QAndroidJniObject::callStaticMethod<void>(
                    "com/company/mri/NotificationClient",
                    "notify",
                    "(Ljava/lang/String;Ljava/lang/String;)V",
                    andIniObjTitle.object<jstring>(),
                    andIniObjBody.object<jstring>());
    
        qDebug() << "Android notification:" << title << body;
    }
    

    My problem is that my application crashes because of an exception from this call:

    F m.company.mr: java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.company.mri.NotificationClient.getSystemService(java.lang.String)' on a null object reference
    F m.company.mr: java_vm_ext.cc:542]   at void com.company.mri.NotificationClient.notify(java.lang.String, java.lang.String) (NotificationClient.java:72)
    F m.company.mr: java_vm_ext.cc:542]   at void org.qtproject.qt5.android.QtNative.startQtApplication() (QtNative.java:-2)
    F m.company.mr: java_vm_ext.cc:542]   at void org.qtproject.qt5.android.QtNative$7.run() (QtNative.java:387)
    F m.company.mr: java_vm_ext.cc:542]   at void org.qtproject.qt5.android.QtThread$1.run() (QtThread.java:61)
    F m.company.mr: java_vm_ext.cc:542]   at void java.lang.Thread.run() (Thread.java:764)
    F m.company.mr: java_vm_ext.cc:542]
    F m.company.mr: java_vm_ext.cc:542]     in call to GetStaticMethodID
    F m.company.mr: java_vm_ext.cc:542]     from void org.qtproject.qt5.android.QtNative.startQtApplication()
    F m.company.mr: java_vm_ext.cc:542] "qtMainLoopThread" prio=5 tid=13 Runnable
    F m.company.mr: java_vm_ext.cc:542]   | group="main" sCount=0 dsCount=0 flags=0 obj=0x12e80dc0 self=0xe456f800
    F m.company.mr: java_vm_ext.cc:542]   | sysTid=15364 nice=0 cgrp=default sched=0/0 handle=0xd27ff970
    F m.company.mr: java_vm_ext.cc:542]   | state=R schedstat=( 284579146 68957989 442 ) utm=19 stm=9 core=3 HZ=100
    F m.company.mr: java_vm_ext.cc:542]   | stack=0xd26fc000-0xd26fe000 stackSize=1042KB
    F m.company.mr: java_vm_ext.cc:542]   | held mutexes= "mutator lock"(shared held)
    F m.company.mr: java_vm_ext.cc:542]   native: #00 pc 004152f6  /system/lib/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+198)
    F m.company.mr: java_vm_ext.cc:542]   native: #01 pc 0051048e  /system/lib/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+382)
    F m.company.mr: java_vm_ext.cc:542]   native: #02 pc 0050b743  /system/lib/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+83)
    F m.company.mr: java_vm_ext.cc:542]   native: #03 pc 0031a8b0  /system/lib/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1088)
    F m.company.mr: java_vm_ext.cc:542]   native: #04 pc 0031ad21  /system/lib/libart.so (art::JavaVMExt::JniAbortV(char const*, char const*, char*)+113)
    F m.company.mr: java_vm_ext.cc:542]   native: #05 pc 000d60f7  /system/lib/libart.so (art::(anonymous namespace)::ScopedCheck::AbortF(char const*, ...)+71)
    F m.company.mr: java_vm_ext.cc:542]   native: #06 pc 000d4a5e  /system/lib/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+1230)
    F m.company.mr: java_vm_ext.cc:542]   native: #07 pc 000d3bdb  /system/lib/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+811)
    F m.company.mr: java_vm_ext.cc:542]   native: #08 pc 000d828a  /system/lib/libart.so (art::(anonymous namespace)::CheckJNI::GetMethodIDInternal(char const*, _JNIEnv*, _jclass*, char const*, char const*, bool)+922)
    F m.company.mr: java_vm_ext.cc:542]   native: #09 pc 000c607e  /system/lib/libart.so (art::(anonymous namespace)::CheckJNI::GetStaticMethodID(_JNIEnv*, _jclass*, char const*, char const*)+46)
    F m.company.mr: java_vm_ext.cc:542]   native: #10 pc 002d5527  /data/app/com.company.mri-wprr13801Hc9xLloo20Lxg==/lib/x86/libQt5Core.so (void QJNIObjectPrivate::callStaticMethod<void>(_jclass*, char const*, char const*, ...)+87)
    F m.company.mr: java_vm_ext.cc:542]   native: #11 pc 000239f0  /data/data/com.company.mri/qt-reserved-files/plugins/platforms/android/libqtforandroid.so (???)
    F m.company.mr: java_vm_ext.cc:542]   at org.qtproject.qt5.android.QtNative.startQtApplication(Native method)
    F m.company.mr: java_vm_ext.cc:542]   at org.qtproject.qt5.android.QtNative$7.run(QtNative.java:387)
    F m.company.mr: java_vm_ext.cc:542]   at org.qtproject.qt5.android.QtThread$1.run(QtThread.java:61)
    F m.company.mr: java_vm_ext.cc:542]   at java.lang.Thread.run(Thread.java:764)
    F m.company.mr: java_vm_ext.cc:542]
    

    I have no idea what is the problem in here. I am not familiar with Java at all. Also the notification example doesnt really explain how is everything connected in here.
    What could be causing this null ptr exception?


  • Qt Champions 2019

    @Bremenpl said in Calling Java method from within Qt yields exception:

    F m.company.mr: java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.company.mri.NotificationClient.getSystemService(java.lang.String)' on a null object reference

    Your m_instance seems to be null: did you create an instance of NotificationClient at some point as you're initialising m_instance in its constructor? You could create an instance in notify(...).



  • @jsulm hi, thanks for answer. I am not familiar with java syntax at all. This example is nearly a copy paste from the qt example where it worked. This is what makes me think that there might be a problem somewhere around the java class within cpp code.


  • Qt Champions 2019

    @Bremenpl It doesn't look like an issue in C++ code - you simply do not have an instance of that Java class. That's all. Take a closer look at the Java code: the instance is assigned to m_instance inside NotificationClient constructor, but since the NotificationClient instance is never created the constructor is not called and m_instance is invalid. Add this line

    m_instance = NotificationClient();
    

    to showNotification (at the beginning) and see what happens.



  • @jsulm i will check that, but could you elaborate why does the example code work?


  • Qt Champions 2019

    @Bremenpl said in Calling Java method from within Qt yields exception:

    but could you elaborate why does the example code work?

    Take a look at this (from the example):

    A NotificationClient object is exposed to the QML in the main source file, main.cpp:
    
    QQuickView view;
    
    NotificationClient *notificationClient = new NotificationClient(&view);
    view.engine()->rootContext()->setContextProperty(QLatin1String("notificationClient"),
                                                         notificationClient);
    

    As you can see in the example an instance of NotificationClient is created, so m_instance is set. I guess you don't have this part of the code in your project?



  • @jsulm no, i dont... Thank you very much. Will check this asap.



  • @jsulm But here is the thing: In some weird way that is not fully understandable to me, this java class is connected with QML, that in my opinion should have nothing to do with anything related to this topic... It seems like somehow the cpp class name NotificationClient being the same as the java class name is not an accident? Is there a way to disengage all those hard coded components from this example?


  • Qt Champions 2019

    @Bremenpl Yes, you're right the code I pasted is C++. I'm not an JNI expert, so I don't know where the instance is created. All I know is what the error message says: there is no instance.
    "F m.company.mr: java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.company.mri.NotificationClient.getSystemService(java.lang.String)' on a null object reference"

    Maybe somebody else has an idea what is missing in your project.

    Does the example application work?



  • @jsulm Yes, the example works perfectly well. But my problem is that I am unable to figure out what is necessary in that example and what is not, since it is not described. For instance, I dont need to export any methods to the QML side. I also have no clue either the cpp NotificationClient class has the same name as java class for a reason, or it is a coincidence. At last, after you have suggested me what is wrong, I dont know how to create the java class instance via cpp without doing any unnecessarily operations on QML (or furthermore, creating an additional cpp class just for this end).



  • So I am just bumping the topic since it feels like I am close (thanks @jsulm). I would really appreciate anyone explaining me the magic happening in that android notofication example (about the namimg and everything)...



  • @Bremenpl I was facing the same problem as you. Maybe my solution is still valuable for you or anyone else reading this thread.

    First of all, also for me the notification example was working without any problems. The above mentioned code snipped

    NotificationClient *notificationClient = new NotificationClient(&view);
    view.engine()->rootContext()->setContextProperty(QLatin1String("notificationClient"),
                                                         notificationClient);
    

    relates to the C++ class NotificationClient used in the example. For me it is really a disadvantage of the notification example to name the C++ class the same way as the Java class. This way it is more confusing to distinguish between the Java class and the C++ class.

    However, like @Bremenpl I also wanted to apply the same principle of calling Java code to my own project. I also was running into the error:

    called with pending exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object com.company.mri.NotificationClient.getSystemService(java.lang.String)' on a null object reference
    

    I checked the notification example and added some debug output to the Java class:

    public NotificationClient()
        {
            Log.i("NotificationClient", "Set instance");
            m_instance = this;
        }
    

    The debug output was shown in the console the QtCreator. I did the same for my own project but there was no debug output. So the static instance of my Java class never has been set. I found a hint to the solution here: http://schorsch.efi.fh-nuernberg.de/roettger/index.php/QtOnAndroid/AppObj
    There it is said that you need to provide an AndroidManifest.xml for the Java class. I did so. My file looks something like this:

    <?xml version="1.0"?>
    <manifest package="org.qtproject.example.myprojectname" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
        <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
        <application android:icon="@drawable/icon" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Project_Label">
            <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.example.myprojectname.MyJavaClassName" android:label="JNI Caller" android:screenOrientation="unspecified">
    
            </activity>
        </application>
        <!-- <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> -->
        <!-- %%INSERT_PERMISSIONS -->
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <!-- %%INSERT_FEATURES -->
    </manifest>
    

    You can basically copy the AndroidManifest.xml from the Notification example and adapt the package and activity name to fit your project's names.

    After adding this file to my own project (OTHER_FILES section in the pro file) and recompiling the project I saw my debug output in the console and was able to run my Java code without running into the NullPointerException! :-)



  • @LtKoerschgen You are right about all symptoms. I also agree with this:

    For me it is really a disadvantage of the notification example to name the C++ class the same way as the Java class. This way it is more confusing to distinguish between the Java class and the C++ class.
    

    Very poor design, especially misleading for someone who doesn't know this API (like me). I was trying to figure out for a while either the naming has anything to do with this.

    I will also leave my extended solution in case anyone needs it:

    package com.company.appname;
    
    import android.app.Notification;
    import android.app.NotificationChannel;
    import android.app.NotificationManager;
    import android.content.Context;
    import android.os.Build;
    
    public class NotificationClient
    {
    	private static final String m_channelId = "channel";
    
    	public static void notify(Context context, String title, String body)
    	{
    		Notification.Builder m_builder;
    
    		if (Build.VERSION.SDK_INT >= 26)
    		{
    			createChannel(context);
    			m_builder = new Notification.Builder(context, m_channelId);
    		}
    	    else
    		{
    			m_builder = new Notification.Builder(context);
    		}
    
    	    NotificationManager m_notificationManager = (NotificationManager)
    		    context.getSystemService(Context.NOTIFICATION_SERVICE);
    
    		m_builder.setContentTitle(title);
    		m_builder.setContentText(body);
    		m_builder.setShowWhen(true);
    
    		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
    		{
    			m_builder.setSmallIcon(R.drawable.icon_trans);
    		}
    	    else
    		{
    			m_builder.setSmallIcon(R.drawable.icon);
    		}
    
    		m_notificationManager.notify(1, m_builder.build());
    	}
    
    	private static void createChannel(Context context)
    	{
    		NotificationChannel channel = new NotificationChannel(
    		    m_channelId, "General", NotificationManager.IMPORTANCE_HIGH);
    		channel.setDescription("The general channel");
    		channel.enableVibration(true);
    
    		NotificationManager notificationManager = (NotificationManager)
    		    context.getSystemService(Context.NOTIFICATION_SERVICE);
    		notificationManager.createNotificationChannel(channel);
    	}
    }
    


  • I was having the same problem, and this thread helped me to solve it.

    The only thing you need to do is to change the android:name attribute in the <activity> tag inside the AndroidManifest.xml file. This name needs to be your package name followed by the class name, so for the OP it needs to be android:name="com.company.mri.NotificationClient"



  • @Bremenpl said in Calling Java method from within Qt yields exception:

    I will also leave my extended solution in case anyone needs it

    Thanks for sharing. In addition, would you mind marking your post as solved?



  • @Wim-van-der-Meer thanks for info. For what end is this change needed?


Log in to reply