Home > other >  My app keeps crashing when I Open the Game Activity. I tried to create a game activity to be opened
My app keeps crashing when I Open the Game Activity. I tried to create a game activity to be opened

Time:06-10

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();
    }
  • Related