When I open my Game Activity with Intent from my Home Activity, the app keeps crashing immediately the game activity launches. The game activity is basically a snake game with canvas to draw out the snake. Please help is there a specific reason for this or is something wrong with my code? I am going to drop my game activity code and manifest below
here is the game activity code
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class GameActivity extends AppCompatActivity implements SurfaceHolder.Callback {
// list snake points / snake length
private List<SnakePoints> snakePointsList = new ArrayList<>();
private SurfaceView surfaceView;
private TextView scoreTV;
// surface holder draws the snake on the surface's canvas
private SurfaceHolder surfaceHolder;
// snake moving position. Values can be right, left, top, bottom.
// by default, snake mover to right.
private String movingPosition = "right";
// score
private int score = 0;
// snake size / point size
private static final int pointSize = 28;
// default tail size
private static final int defaultTailPoints = 3;
// snake color
private static final int snakeColor = Color.YELLOW;
// snake speed
private static final int snakeMovingSpeed = 800;
// random point coordinates on the surfaceView
private int positionX, positionY;
// timer to move snake / change snake position after a specific time
private Timer timer;
// canvas to draw snake and show on surfaceView
private Canvas canvas = null;
//point color / single point color of a snake
private Paint pointColor = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
// getting surfaceView and score TextView from xml file
surfaceView = findViewById(R.id.surfaceView);
scoreTV = findViewById(R.id.scoreTV);
// getting image buttons from xml file
final AppCompatImageButton topBtn = findViewById(R.id.topBtn);
final AppCompatImageButton leftBtn = findViewById(R.id.leftBtn);
final AppCompatImageButton rightBtn = findViewById(R.id.rightBtn);
final AppCompatImageButton bottomBtn = findViewById(R.id.bottomBtn);
// call back to surfaceView
surfaceView.getHolder().addCallback(this);
topBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!movingPosition.equals("bottom")){
movingPosition = "top";
}
}
});
leftBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!movingPosition.equals("right")){
movingPosition = "left";
}
}
});
rightBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!movingPosition.equals("left")){
movingPosition = "right";
}
}
});
bottomBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!movingPosition.equals("top")){
movingPosition = "bottom";
}
}
});
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
// when the surface is created, get surfaceHolder and assign it to surfaceHolder
this.surfaceHolder = surfaceHolder;
// initialize data for snake / surfaceView
init();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
private void init(){
// clear the snake points / length
snakePointsList.clear();
// set default score
scoreTV.setText("0");
// make score 0
score = 0;
// default moving position is right
movingPosition = "right";
// default snake starting position on the screen
int startPositionX = (pointSize) * defaultTailPoints;
// making snake's default length / points
for (int i = 0; i < defaultTailPoints; i ){
// adding points to snake tail
SnakePoints snakePoints = new SnakePoints(startPositionX, pointSize);
snakePointsList.add(snakePoints);
// increasing the value for the next snake tail
startPositionX = startPositionX - (pointSize * 2);
}
// add random point on the screen to be eaten by snake
addPoint();
// start moving the snake / start game
moveSnake();
}
private void addPoint(){
// getting surfaceView width and height to add point on the surface to be eaten by the snake
int surfaceWidth = surfaceView.getWidth() - (pointSize * 2);
int surfaceHeight = surfaceView.getHeight() - (pointSize * 2);
int randomXPosition = new Random().nextInt(surfaceWidth / pointSize);
int randomYPosition = new Random().nextInt(surfaceHeight / pointSize);
//check if randomXPosition is even orr odd value coz we need only even number
if ((randomXPosition % 2) != 0){
randomXPosition = randomXPosition 1;
}
if ((randomYPosition % 2) != 0){
randomYPosition = randomYPosition 1;
}
positionX = (pointSize * randomXPosition) pointSize;
positionY = (pointSize * randomYPosition) pointSize;
}
private void moveSnake(){
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// getting head position
int headPositionX = snakePointsList.get(0).getPositionX();
int headPositionY = snakePointsList.get(0).getPositionY();
// to check if the snake has eaten a point
if (headPositionX == positionX && positionY == headPositionY){
//grow snake after eating
growSnake();
// add another random point on the screen
addPoint();
}
// check which side the snake is moving
switch (movingPosition){
case "right":
//move snakes head to right
snakePointsList.get(0).setPositionX(headPositionX (pointSize * 2));
snakePointsList.get(0).setPositionY(headPositionY);
break;
case "left":
//move snakes head to left
snakePointsList.get(0).setPositionX(headPositionX - (pointSize * 2));
snakePointsList.get(0).setPositionY(headPositionY);
break;
case "top":
//move snakes head to top
snakePointsList.get(0).setPositionX(headPositionX);
snakePointsList.get(0).setPositionY(headPositionY - (pointSize * 2));
break;
case "bottom":
//move snakes head to bottom
snakePointsList.get(0).setPositionX(headPositionX);
snakePointsList.get(0).setPositionY(headPositionY (pointSize * 2));
break;
}
//check if game is over. Weather snake touch edges or snake itself
if (checkGameOver(headPositionX, headPositionY)){
//stop timer / stop moving snake
timer.purge();
timer.cancel();
//game over dialogue
AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);
builder.setMessage("Your Score = " score);
builder.setTitle("Game Over");
builder.setCancelable(false);
builder.setPositiveButton("Go back", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// return
Intent intent = new Intent(GameActivity.this, HomeActivity.class);
startActivity(intent);
}
});
//timer runs in background so show dialogue in main thread
runOnUiThread(new Runnable() {
@Override
public void run() {
builder.show();
}
});
}
else {
//lock canvas on surfaceHolder to draw on it
canvas = surfaceHolder.lockCanvas();
//clear canvas with white color
canvas.drawColor(Color.WHITE, PorterDuff.Mode.CLEAR);
// change snake's head position. Other snake points will follow snake's head
canvas.drawCircle(snakePointsList.get(0).getPositionX(), snakePointsList.get(0).getPositionY(), pointSize, createPointColor());
// draw random point circles on the surface to be eaten by the snake
canvas.drawCircle(positionX, positionY, pointSize, createPointColor());
// all other points following snake's head position 0 is head of snake
for (int i = 1; i < snakePointsList.size(); i ){
int getTempPositionX = snakePointsList.get(i).getPositionX();
int getTempPositionY = snakePointsList.get(i).getPositionY();
// move points across the head
snakePointsList.get(i).setPositionX(headPositionX);
snakePointsList.get(i).setPositionY(headPositionY);
canvas.drawCircle(snakePointsList.get(i).getPositionX(), snakePointsList.get(i).getPositionY(), pointSize, createPointColor());
//change head position
headPositionX = getTempPositionX;
headPositionY = getTempPositionY;
}
//unlock canvas to draw on surfaceView
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}, 1000- snakeMovingSpeed, 1000- snakeMovingSpeed);
}
private void growSnake(){
// create new snake points
SnakePoints snakePoints = new SnakePoints(0,0);
//add points to the snake's tail
snakePointsList.add(snakePoints);
//increase score
score ;
//setting score to textView
runOnUiThread(new Runnable() {
@Override
public void run() {
scoreTV.setText(String.valueOf(score));
}
});
}
private boolean checkGameOver(int headPositionX, int headPositionY){
boolean gameOver = false;
//check if snake's head touches edges
if (snakePointsList.get(0).getPositionX() < 0 ||
snakePointsList.get(0).getPositionY() < 0 ||
snakePointsList.get(0).getPositionX() >= surfaceView.getWidth() ||
snakePointsList.get(0).getPositionY() >= surfaceView.getHeight()){
gameOver = true;
}
else {
//check if snake's head touches itself
for (int i = 1; i < snakePointsList.size(); i ){
if (headPositionX == snakePointsList.get(i).getPositionX() &&
headPositionY == snakePointsList.get(i).getPositionY()){
gameOver = true;
break;
}
}
}
return gameOver;
}
private Paint createPointColor(){
if (pointColor == null){
pointColor = new Paint();
pointColor.setColor(snakeColor);
pointColor.setStyle(Paint.Style.FILL);
pointColor.setAntiAlias(true);
}
return pointColor;
}
}
here is my manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.practechs.kubetnew28_05">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:exported="false"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/Theme.KubetNew25_05">
<activity
android:name=".GameActivity"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"/>
<activity
android:name=".WebsiteActivity"
android:exported="false" />
<activity
android:name=".HomeActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The error displayed in my logcat
2022-06-08 14:21:13.881 19048-19242/com.practechs.kubetnew28_05 E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.practechs.kubetnew28_05, PID: 19048
java.lang.NullPointerException: Attempt to invoke interface method 'android.graphics.Canvas android.view.SurfaceHolder.lockCanvas()' on a null object reference
at com.practechs.kubetnew28_05.GameActivity$5.run(GameActivity.java:290)
at java.util.TimerThread.mainLoop(Timer.java:562)
at java.util.TimerThread.run(Timer.java:512)
CodePudding user response:
The stack trace shows you the method and line in which the NPE occurs:
...SurfaceHolder.lockCanvas()...
...(GameActivity.java:290)
This happens because your SurfaceHolder
variable is null
until you set it in surfaceCreated
:
private SurfaceHolder surfaceHolder;
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
this.surfaceHolder = surfaceHolder;
init();
}
And here lies the problem, instead of setting the created SurfaceHolder
you set the class variable on itself. The created SurfaceHolder
is called holder not surfaceHolder.
You can fix it by renaming the method parameter:
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
init();
}
or assign the method parameter correctly.
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
this.surfaceHolder = holder;
init();
}