Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't toast on a thread that has not called Looper.prepare()

Tags:

android

I try to run a test for my android app but I get this trace. What does it mean?

java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare() at android.widget.Toast$TN.<init>(Toast.java:390) at android.widget.Toast.<init>(Toast.java:114) at android.widget.Toast.makeText(Toast.java:277) at android.widget.Toast.makeText(Toast.java:267) at dev.android.gamex.CatchGame.onDraw(MainActivity.java:317) at dev.android.gamex.JamieTest.useAppContext(JamieTest.java:45) at java.lang.reflect.Method.invoke(Native Method) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37) at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at org.junit.runner.JUnitCore.run(JUnitCore.java:115) at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58) at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:375) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074)  Tests ran to completion. 

My test class

package dev.android.gamex;   import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.support.test.InstrumentationRegistry; import android.widget.TextView;  import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner;  import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when;  @RunWith(MockitoJUnitRunner.class) public class JamieTest {      private static final String FAKE_STRING = "HELLO WORLD";      private OnScoreListener onScoreListener = new OnScoreListener() {         @Override         public void onScore(int score) {         }     };      @Mock     Canvas can;      @Test     public void useAppContext() throws Exception {         Context appContext = InstrumentationRegistry.getTargetContext();         assertEquals("dev.android.gamex", appContext.getPackageName());         CatchGame cg = new CatchGame(appContext, 5, "Jamie", onScoreListener);         cg.initialize();         assertTrue(! cg.gameOver);         cg.onDraw(new Canvas());         assertTrue(! cg.paused);      } } 

The code I want to test is below.

package dev.android.gamex;  import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.support.v4.view.MotionEventCompat; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast;  import java.util.Random;  public class MainActivity extends AppCompatActivity {     CatchGame cg;     public TextView textView;     public LinearLayout mainLayout;     String[] spinnerValue = {"Rookie", "Advanced", "Expert", "Master"};     // start app     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         mainLayout = new LinearLayout(this);         mainLayout.setOrientation(LinearLayout.VERTICAL);          LinearLayout menuLayout = new LinearLayout(this);         menuLayout.setBackgroundColor(Color.parseColor("#FFFFFF"));          textView = new TextView(this);         textView.setVisibility(View.VISIBLE);         String str = "Score: 0";         textView.setText(str);         menuLayout.addView(textView);          Button button = new Button(this);         button.setText("Pause");         button.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 togglePausePlay();             }         });         menuLayout.addView(button);           Spinner spinner2 =new Spinner(this);         ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, spinnerValue);         spinner2.setAdapter(adapter);         menuLayout.addView(spinner2);          mainLayout.addView(menuLayout);          cg = new CatchGame(this, 5, "Jamie", onScoreListener);         cg.setBackground(getResources().getDrawable(R.drawable.bg_land_mdpi));         mainLayout.addView(cg);         getWindow().requestFeature(Window.FEATURE_ACTION_BAR);         getSupportActionBar().hide();         setContentView(mainLayout);     }      private void togglePausePlay() {         if (cg.paused) {             // play             //  getSupportActionBar().hide();             Toast.makeText(MainActivity.this, "Play", Toast.LENGTH_SHORT).show();         } else {             // pause             //    getSupportActionBar().show();             Toast.makeText(MainActivity.this, "Pause", Toast.LENGTH_SHORT).show();         }          cg.paused = !cg.paused;     }      private OnScoreListener onScoreListener = new OnScoreListener() {         @Override         public void onScore(int score) {             textView.setText("Score: " + score);         }     };      @Override     public boolean onCreateOptionsMenu(Menu menu) {         // Inflate the menu; this adds items to the action bar if it is present.         getMenuInflater().inflate(R.menu.main_menu, menu);         return true;     }      // method called when top right menu is tapped     @Override     public boolean onOptionsItemSelected(MenuItem item) {         super.onOptionsItemSelected(item);         int difficulty = cg.NBRSTEPS;         String name = cg.heroName;          switch (item.getItemId()) {             case R.id.item11:                 cg = new CatchGame(this, 3, name, onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item12:                 cg = new CatchGame(this, 5, name, onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item13:                 cg = new CatchGame(this, 7, name, onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item14:                 cg = new CatchGame(this, 9, name, onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item15:                 cg = new CatchGame(this, 11, name, onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item21:                 cg = new CatchGame(this, difficulty, "Jamie", onScoreListener);                 setContentView(cg);                 mainLayout.addView(cg);                 getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 setContentView(mainLayout);                 return true;             case R.id.item22:                 cg = new CatchGame(this, difficulty, "Spaceship", onScoreListener);                 setContentView(cg);                 //mainLayout.addView(cg);                 //getWindow().requestFeature(Window.FEATURE_ACTION_BAR);                 getSupportActionBar().hide();                 //setContentView(mainLayout);                 return true;             default:                 cg.paused = true;                 return super.onOptionsItemSelected(item);         }     }  }  interface OnScoreListener {     void onScore(int score); }  class CatchGame extends View {     int NBRSTEPS; // number of discrete positions in the x-dimension; must be uneven     String heroName;     int screenW;     int screenH;     int[] x; // x-coordinates for falling objects     int[] y; // y-coordinates for falling objects     int[] hero_positions; // x-coordinates for hero     Random random = new Random();     int ballW; // width of each falling object     int ballH; // height of ditto     float dY; //vertical speed     Bitmap falling, hero, jamie2, jamieleft, jamieright;     int heroXCoord;     int heroYCoord;     int xsteps;     int score;     int offset;     boolean gameOver; // default value is false     boolean toastDisplayed;     boolean paused = false;      OnScoreListener onScoreListener;      // constructor, load images and get sizes     public CatchGame(Context context, int difficulty, String name, OnScoreListener onScoreListener) {         super(context);         NBRSTEPS = difficulty;         heroName = name;         this.onScoreListener = onScoreListener;          x = new int[NBRSTEPS];         y = new int[NBRSTEPS];         hero_positions = new int[NBRSTEPS];         int resourceIdFalling = 0;         int resourceIdHero = 0;         if (heroName.equals("Jamie")) {             resourceIdFalling = R.mipmap.falling_object2;             resourceIdHero = R.drawable.left_side_hdpi;             setBackground(getResources().getDrawable(R.mipmap.background));         }         if (heroName.equals("Spaceship")) {             resourceIdFalling = R.mipmap.falling_object;             resourceIdHero = R.mipmap.ufo;             setBackground(getResources().getDrawable(R.mipmap.space));         }         falling = BitmapFactory.decodeResource(getResources(), resourceIdFalling); //load a falling image         hero = BitmapFactory.decodeResource(getResources(), resourceIdHero); //load a hero image         jamieleft = BitmapFactory.decodeResource(getResources(), R.drawable.left_side_hdpi); //load a hero image         jamieright = BitmapFactory.decodeResource(getResources(), R.drawable.right_side_hdpi); //load a hero image          ballW = falling.getWidth();         ballH = falling.getHeight();     }      public CatchGame(Context context, int difficulty, String name, OnScoreListener onScoreListener, Drawable background) {         this(context, difficulty, name, onScoreListener);         this.setBackground(background);     }      // set coordinates, etc.     void initialize() {         if (!gameOver) { // run only once, when the game is first started             int maxOffset = (NBRSTEPS - 1) / 2;             for (int i = 0; i < x.length; i++) {                 int origin = (screenW / 2) + xsteps * (i - maxOffset);                 x[i] = origin - (ballW / 2);                 hero_positions[i] = origin - hero.getWidth();             }             int heroWidth = hero.getWidth();             int heroHeight = hero.getHeight();              hero = Bitmap.createScaledBitmap(hero, heroWidth * 2, heroHeight * 2, true);             hero = Bitmap.createScaledBitmap(hero, heroWidth * 2, heroHeight * 2, true);             jamieleft = Bitmap.createScaledBitmap(jamieleft, jamieleft.getWidth()* 2, jamieright.getWidth() * 2, true);             jamieright = Bitmap.createScaledBitmap(jamieright, jamieright.getWidth()* 2, jamieright.getWidth() * 2, true);              heroYCoord = screenH - 2 * heroHeight; // bottom of screen          }         for (int i = 0; i < y.length; i++) {             y[i] = -random.nextInt(1000); // place items randomly in vertical direction         }          offset = (NBRSTEPS - 1) / 2; // place hero at centre of the screen         heroXCoord = hero_positions[offset];          // initialize or reset global attributes         dY = 2.0f;         score = 0;         gameOver = false;         toastDisplayed = false;     }      // method called when the screen opens     @Override     public void onSizeChanged(int w, int h, int oldw, int oldh) {         super.onSizeChanged(w, h, oldw, oldh);         screenW = w;         screenH = h;         xsteps = w / NBRSTEPS;         initialize();     }      // method called when the "game over" toast has finished displaying     void restart(Canvas canvas) {          toastDisplayed = true;         initialize();         draw(canvas);     }      // update the canvas in order to display the game action     @Override     public void onDraw(Canvas canvas) {         if (toastDisplayed) {             restart(canvas);             return;         }         super.onDraw(canvas);          int heroHeight = hero.getHeight();         int heroWidth = hero.getWidth();         int heroCentre = heroXCoord + heroWidth / 2;          Context context = this.getContext();          // compute locations of falling objects         for (int i = 0; i < y.length; i++) {             if (!paused) {                 y[i] += (int) dY;             }             // if falling object hits bottom of screen             if (y[i] > (screenH - ballH) && !gameOver) {                 dY = 0;                 gameOver = true;                 paused = true;                 int duration = Toast.LENGTH_SHORT;                  final Toast toast = Toast.makeText(context, "GAME OVER!\nScore: " + score, duration);                 toast.show();                 Handler handler = new Handler();                 handler.postDelayed(new Runnable() {                     @Override                     public void run() {                         toast.cancel();                         toastDisplayed = true;                     }                 }, 3000);                 //Vibrator v = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);                 // Vibrate for 3000 milliseconds                 //v.vibrate(3000);              }             // if the hero catches a falling object             if (x[i] < heroCentre && x[i] + ballW > heroCentre &&                     y[i] > screenH - ballH - heroHeight) {                  y[i] = -random.nextInt(1000); // reset to new vertical position                 score += 1;                 onScoreListener.onScore(score);             }          }          canvas.save(); //Save the position of the canvas.          for (int i = 0; i < y.length; i++) {             canvas.drawBitmap(falling, x[i], y[i], null); //Draw the falling on the canvas.         }         canvas.drawBitmap(hero, heroXCoord, heroYCoord, null); //Draw the hero on the canvas.          canvas.restore();         //Call the next frame.         invalidate();     }      // event listener for when the user touches the screen     @Override     public boolean onTouchEvent(MotionEvent event) {         if (paused) {             paused = false;         }         int action = MotionEventCompat.getActionMasked(event);         if (action != MotionEvent.ACTION_DOWN || gameOver) { // non-touchdown event or gameover             return true; // do nothing         }         int coordX = (int) event.getX();         int xCentre = (screenW / 2) - (hero.getWidth() / 2);         int maxOffset = hero_positions.length - 1; // can't move outside right edge of screen         int minOffset = 0; // ditto left edge of screen          if (coordX < xCentre && offset > minOffset) { // touch event left of the centre of screen             offset--; // move hero to the left              if(coordX < heroXCoord)// + heroWidth / 2)                 hero = Bitmap.createScaledBitmap(jamieleft, jamieleft.getWidth() , jamieleft.getHeight() , true);          }         if (coordX > xCentre && offset < maxOffset) { // touch event right of the centre of screen             offset++; // move hero to the right              if(coordX > heroXCoord)                 hero  = Bitmap.createScaledBitmap(jamieright, jamieright.getWidth() , jamieright.getHeight() , true);          }         heroXCoord = hero_positions[offset];          return true;     } } 

The repository is available online.

like image 224
Niklas Rosencrantz Avatar asked Nov 28 '17 16:11

Niklas Rosencrantz


People also ask

Can't toast on a thread that has not?

You CANNOT show a Toast on non-UI thread. You need to call Toast. makeText() (and most other functions dealing with the UI) from within the main thread. runOnUiThread(new Runnable() { public void run() { final Toast toast = Toast.

What is looper prepare?

android.os.Looper. Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

What is UI thread in Android?

User Interface Thread or UI-Thread in Android is a Thread element responsible for updating the layout elements of the application implicitly or explicitly. This means, to update an element or change its attributes in the application layout ie the front-end of the application, one can make use of the UI-Thread.

What is a handler in Android?

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue . Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper .


2 Answers

You CANNOT show a Toast on non-UI thread. You need to call Toast.makeText() (and most other functions dealing with the UI) from within the main thread.


You could use Activity#runOnUiThread():

runOnUiThread(new Runnable() {       public void run() {          final Toast toast = Toast.makeText(context, "GAME OVER!\nScore: " + score, duration);          toast.show();       }  }); 

If you want execute a instrumentation test on main thread, add @UiThreadTest annotation:

@Test @UiThreadTest public void useAppContext() {     // ... } 

P.s: There are also many other ways with explain (using Handler, Looper, Observable..) in these posts: Android: Toast in a thread and Can't create handler inside thread that has not called Looper.prepare()

like image 110
nhoxbypass Avatar answered Oct 08 '22 19:10

nhoxbypass


One cannot show a Toast from a non UI Thread. So you can do the following from the worker thread and it doesn't require Activity or Context

JAVA

 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() {     Toast toast = Toast.makeText(mContext, "Toast", Toast.LENGTH_SHORT);     toast.show(); } }); 

KOTLIN

Handler(Looper.getMainLooper()).post {     Toast.makeText(mContext, "Toast", Toast.LENGTH_SHORT).show() } 
like image 44
akhil nair Avatar answered Oct 08 '22 20:10

akhil nair