The app works fine if I wait a bit after clicking play, but if I want click on anything clickable right away, the app crashes.
Here is the error message:
E/InputEventReceiver: Exception dispatching input event. D/AndroidRuntime: Shutting down VM E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.combatcats, PID: 13769 java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Boolean.booleanValue()' on a null object reference at com.example.combatcats.Joystick.getIsPressed(Joystick.java:86) at com.example.combatcats.GameView.onTouchEvent(GameView.java:187) at android.view.View.dispatchTouchEvent(View.java:14540) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801) at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:502) at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1890) at android.app.Activity.dispatchTouchEvent(Activity.java:4196) at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69) at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:460) at android.view.View.dispatchPointerEvent(View.java:14799) at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6347) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6148) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5626) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5814) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5871) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5683) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5649) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5657) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5630) at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8562) at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8513) at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:8482) at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8685) at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:259) at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method) at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:239) at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:8642) at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:8771) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037) at android.view.Choreographer.doCallbacks(Choreographer.java:845) at android.view.Choreographer.doFrame(Choreographer.java:772) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) E/AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Here is my GameView
class:
package com.example.combatcats; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceView; public class GameView extends SurfaceView implements Runnable { private final Joystick joystick; private Thread thread; private boolean isPlaying; private int screenX, screenY; public static float screenRatioX, screenRatioY; private Paint paint; private Cat cat; private Background background1, background2; private int InAirCounter; private final MainMenuButton mainMenuButton; private GameScreen activity; public GameView(GameScreen activity, int screenX, int screenY) { super(activity); this.activity = activity; this.screenX = screenX; this.screenY = screenY; screenRatioX = 1920f / screenX; screenRatioY = 1080f / screenY; background1 = new Background(screenX, screenY, getResources()); background2 = new Background(screenX, screenY, getResources()); cat = new Cat(screenY, getResources()); background2.x = screenX; paint = new Paint(); joystick = new Joystick(190,900,90,55); mainMenuButton = new MainMenuButton(60,60,250,158); } @Override public void run() { while (isPlaying) { update (); draw (); sleep (); } } private void update () { background1.x -= 10 * screenRatioX; background2.x -= 10 * screenRatioX; InAirCounter -= 1; if (background1.x + background1.background.getWidth() < 0) { background1.x = screenX; } if (background2.x + background2.background.getWidth() < 0) { background2.x = screenX; } if (cat.y == (screenY - cat.height) && cat.isJumping) { InAirCounter = 7; } if (InAirCounter > 0) cat.y -= 30 * screenRatioY; else cat.y += 30 * screenRatioY; if (cat.y < 0) cat.y = 0; if (cat.y > screenY - cat.height) cat.y = screenY - cat.height; if (cat.x < 0) cat.x = 0; if (cat.x > screenX - cat.width) cat.x = screenX - cat.width; //Log.d("","var_name = "+var); cat.update(joystick); joystick.update(); } private void draw () { if (getHolder().getSurface().isValid()) { Canvas canvas = getHolder().lockCanvas(); canvas.drawBitmap(background1.background, background1.x, background1.y, paint); canvas.drawBitmap(background2.background, background2.x, background2.y, paint); canvas.drawBitmap(cat.getBlink(), cat.x, cat.y, paint); joystick.draw(canvas); mainMenuButton.draw(canvas); getHolder().unlockCanvasAndPost(canvas); } } private void sleep () { try { Thread.sleep(17); } catch (InterruptedException e) { e.printStackTrace(); } } public void resume () { isPlaying = true; thread = new Thread(this); thread.start(); } public void pause () { try { isPlaying = false; thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (event.getX() > screenX / 2 && event.getY() > screenY / 2) { cat.isJumping = true; } if(joystick.isPressed((double) event.getX(), (double) event.getY())){ joystick.setIsPressed(true); } if(event.getX() >= mainMenuButton.getValue(1) && event.getX() <= mainMenuButton.getValue(3) && event.getY() >= mainMenuButton.getValue(2) && event.getY() <= mainMenuButton.getValue(4)) { isPlaying = false; activity.startActivity(new Intent(activity, MainActivity.class)); activity.finish(); } break; case MotionEvent.ACTION_MOVE: if(joystick.getIsPressed()) { joystick.setActuator((double) event.getX(), (double) event.getY()); } break; case MotionEvent.ACTION_UP: joystick.setIsPressed(false); joystick.resetActuator(); cat.isJumping = false; break; } return true; } }
Advertisement
Answer
The call stack
virtual method 'boolean java.lang.Boolean.booleanValue()' on a null object reference at com.example.combatcats.Joystick.getIsPressed(Joystick.java:86) at com.example.combatcats.GameView.onTouchEvent(GameView.java:187) at
tells you that in the method Joystick.getIsPressed
you are trying to convert a Boolean
object to a boolean
by invoking booleanValue()
on it, but that object is null
.
You didn’t show the code for Joystick
but I suspect it looks something like this:
class Joystick { Boolean pressed; void setIsPressed(boolean pressed){ this.pressed = pressed; } boolean isPressed() { return pressed.getBooleanValue(); }
The important part is that pressed
doesn’t get initialised on construction.
If we inspect your GameView.onTouchEvent
method you’ll note that setIsPressed
is invoked on ACTION_DOWN
and ACTION_UP
, while isPressed
is invoked on ACTION_MOVE
.
When you wait a little until the UI is fully initialized ACTION_MOVE
will probably always happen after ACTION_DOWN
and therefore everything is fine.
But when spam clicking it might happen that the ACTION_DOWN event doesn't get forwarded to your view, because it is not ready yet. But
ACTION_MOVEis. Now this is the first event you process,
pressed` is not yet initialized and you get the NPE you are seeing.
The solution is to either initialize pressed
on construction of Joystick
or even better: make it a primitive boolean
in the first place.