Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I'm writing an app in Kotlin, which displays 2D object on user's phone. When I execute my code, I get Java.lang.nullpointerexceptionRun. Sadly, I don't know how to fix this issue. Run output:
E/com.a3dmodelap: Invalid ID 0x00000000.
E/AndroidRuntime: FATAL EXCEPTION: GLThread 633
    Process: com.a3dmodelapp, PID: 7503
    java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
        at com.a3dmodelapp.Texture.load(Texture.kt:80)
        at com.a3dmodelapp.Stage$MyRenderer.onSurfaceCreated(Stage.kt:91)
        at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1541)
        at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1272)

So basically it fails at this line (Texture.kt:80):
Quote:
width = bmp.getWidth()

I'm new to Kotin and already spent hours trying to fix it. Would greatly appreciate the help. Here's the code in Texture class:

...
private var width : Int = 0
private var height : Int = 0
var resourceId = 0
...
constructor(resourceId: Int, mipmaps: Boolean)
{
   this.resourceId = resourceId
   this.textureId = -1
   this.mipmaps = mipmaps
}
constructor(resourceId: Int) {
   this(resourceId, false)
   Texture(resourceId, false)
}
...
fun getWidth(): Int 
{
   return width
}

fun getHeight(): Int
{
   return height
}
...
fun load(context: Context)
{
   val opts = BitmapFactory.Options()
   opts.inScaled = false
   val bmp = BitmapFactory.decodeResource(context.getResources(), resourceId, opts)
   width = bmp.getWidth()
   height = bmp.getHeight()
...
}


UPDATE: It seems that resourceId is the problem. bmp variable is null, because of resourceId being 0. Followed my code step by step and I can't figure out where the problem may be. Right down below I provide more in depth walktrough of resourceId variable.
In Stage class I have a call to the constructor:
tex = Texture(getResources().getIdentifier(img, "drawable", context?.getPackageName()))

After checking, it is basically the same as this:
tex = Texture(2131165330)

So that means it's calling the second constructor in Texture class:
constructor(resourceId: Int) 
{
   Texture(resourceId, false)
}

And then the first one:
constructor(resourceId: Int, mipmaps: Boolean) 
{
   this.resourceId = resourceId
   this.textureId = -1
   this.mipmaps = mipmaps
    }

resourceId shouldn't be equal to 0, but to 2131165330 (I've checked and it is being set in the first constructor). I don't know why it's equal to 0

UPDATE 2
Below I provide you Stage class (the most relevant to me lines of code have been bolded):
class Stage(context: Context?, attrs: AttributeSet?) : GLSurfaceView(context, attrs) {

    private var w = 0.0F
    private var h = 0.0F
    private var screenHeight = 0
    private var screenWidth = 0
    private lateinit var vertexBuffer: FloatBuffer
    private lateinit var tex: Texture
    private var img = ""
    private var xPos = 0.0F
    private var yPos = 0.0F
    private var r = 0.0F

    init {
        setEGLConfigChooser(8, 8, 8, 8, 0, 0)
        setRenderer(MyRenderer())

        val vertices = floatArrayOf(
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f,
            -0.5f, 0.5f, 0.0f,
            0.5f, 0.5f, 0.0f,
        )

        val vbb: ByteBuffer = ByteBuffer.allocateDirect(vertices.size * 4)
        vbb.order(ByteOrder.nativeOrder())
        vertexBuffer = vbb.asFloatBuffer()
        vertexBuffer.put(vertices)
        vertexBuffer.position(0)

        img = "football"
        tex = Texture(getResources().getIdentifier(img, "drawable", context?.getPackageName()))
    }

    private inner class MyRenderer : Renderer {
        override fun onDrawFrame(gl: GL10) {
            gl.glClear(GLES10.GL_COLOR_BUFFER_BIT)
            tex.prepare(gl, GL10.GL_CLAMP_TO_EDGE)
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer)
            tex.draw(gl, xPos, yPos, tex.getWidth()*r, tex.getHeight()*r, 0f)
        }
        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            gl.glClearColor(0f, 0f, 0f, 1.0f)
            if (width > height) {
                h = 600F
                w = width * h / height
            } else {
                w = 600F
                h = height * w / width
            }
            screenWidth = width
            screenHeight = height

            xPos = w/2
            yPos = h/2
            r= 1F

            gl.glViewport(0, 0, screenWidth, screenHeight)
            gl.glMatrixMode(GL10.GL_PROJECTION)
            gl.glLoadIdentity()
            gl.glOrthof(0f, w, h, 0f, -1f, 1f)
            gl.glMatrixMode(GL10.GL_MODELVIEW)
            gl.glLoadIdentity()
        }

        override fun onSurfaceCreated(gl: GL10?, p1: EGLConfig?) {
            // Set up alpha blending
            gl?.apply {
                glEnable(GL10.GL_ALPHA_TEST)
                glEnable(GL10.GL_BLEND)
                glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA)
                // We are in 2D, so no need depth
                glDisable(GL10.GL_DEPTH_TEST)
                // Enable vertex arrays (we'll use them to draw primitives).
                glEnableClientState(GL10.GL_VERTEX_ARRAY)

                gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY)
                tex.load(getContext())
            }
        }
    }
}


UPDATE 3
I think I've found where the issue is, but I don't know how to solve it. Right here I'm making a call to the second constructor:
tex = Texture(getResources().getIdentifier(img, "drawable", context?.getPackageName()))

In the second constructor, I call the first one, but it doesn't make any sense, because it doesn't save values back to the Stage class. What I want to do is create something like this:
constructor(resourceId: Int) 
{
   this(resourceId, false)
}

In Java it would work perfectly fine, but here in Kotlin it doesn't. This is the only line that needs work, because if I make a call like the one provided below everything works just fine:
tex = Texture(getResources().getIdentifier(img, "drawable", context?.getPackageName()), false)


What I have tried:

I've spent hours trying to solve this with no success
Posted
Updated 2-Jan-22 11:59am
v16
Comments
0x01AA 2-Jan-22 14:44pm    
How is about to check wheter val bmp = BitmapFactory.decodeResource(context.getResources(), resourceId, opts) returns a 'not null' value? And in case it returns null you need to investigate why...
Luc Pattyn 2-Jan-22 15:34pm    
I would expect one (or several) texture files inside the drawable folder, and then a "drawable.myTextureFileName" in your Texture creating statement tex = Texture(...);

EDIT: have been reading some more; my first paragraph seems incorrect now. However, is your texture really called "img" ? rather than a filename with a typical image extension? Maybe try once again, with some .png file or similar.

BTW: when you improve your question (which is OK) you should also reply to an appropriate solution or comment in order to send a signal to others. Just changing the question creates no signal to anyone...
Member 15127345 2-Jan-22 16:36pm    
Thank you for your help! img variable is fine. I say that, because when I hardcode resourceId variable (basically set resourceId to 2131165330) right before bmp, my texture loads correctly. I've updated my question and provided also Stage class, maybe there lies my problem. Thanks for the tips
Luc Pattyn 2-Jan-22 17:25pm    
some ideas:
- do you have a file called "football" inside the "drawable" folder?
- have you tried with a more appropriate filename, with one of the standard image extensions (such as .png)?
- are you using "configuration-specific alternatives"? if so look here ( https://stackoverflow.com/questions/23375593/how-do-i-load-a-texture-from-a-config-file ) and make sure you also have such file in the default drawable.

Now I ran out of ideas.
-
Luc Pattyn 2-Jan-22 17:42pm    
correction, one last suggestion:
please check context?.getPackageName() where you use it. It should NOT be null!

it looks like bmp is null due to resourceId being zero. And you did not show any code that sets resourceId...
 
Share this answer
 
Comments
0x01AA 2-Jan-22 15:07pm    
Now I make a test and post my comment as an answer ;)
Member 15127345 2-Jan-22 15:38pm    
Thank you for the comment! Indeed, bmp is null due to resourceId, which is equal to 0. Still can't figure out where lies my problem. Updated my code showing how resourceId has been set
Luc Pattyn 2-Jan-22 16:35pm    
FYI: I've added, later modified, a comment to your original question. It's about filenames.
How is about to check wheter val bmp = BitmapFactory.decodeResource(context.getResources(), resourceId, opts) returns a 'not null' value? And in case it returns null you need to investigate why...
 
Share this answer
 
Comments
Luc Pattyn 2-Jan-22 15:12pm    
I think we already know why: the very first message said E/com.a3dmodelap: Invalid ID 0x00000000., so what remains to be done is find out why there is a zero ID.
Member 15127345 2-Jan-22 15:41pm    
Thank you for your help! I've checked and bmp returns a null value caused by resourceId being 0. Updated my question showing how resourceId has been set. I still can't figure out why resourceId is 0
The issue was in the second constructor. I had to change one line of code.
constructor(resourceId: Int): this(resourceId, false)

Reason why I had to do this: I was calling another constructor, but I wasn't saving values back to the Stage class and that's why resourceId was equal to 0.
I really do appreciate your help! Thank you so much!
 
Share this answer
 
v3
Comments
Luc Pattyn 2-Jan-22 18:06pm    
You're welcome.
I'm glad that went swiftly :D

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900