Trying to show Alert Dialog for messages, while rendering objects in background. Can't get the gist of this.

Started by
3 comments, last by tom_mai78101 11 years, 10 months ago
Goal:

I wanted to show a dialog pop-up to the player (user), to tell them a message, such as the high score, obtaining user names, etc. for the scoreboard. Anything involving a dialog and user input, all is welcome.

Design:

I have a background rendering Canvas, displaying the basic objects on screen. And then there's the dialog. When a player meets a simple goal, the dialog should pop-up and congratulates the player for completing the goal, and then returns to the screen, where the screen itself continues to be rendering/drawing the objects onto the screen.

Problem:

When calling a dialog to show up, I do not understand why it is required to called upon Looper.prepare() and Looper.loop(). And how do you get back to the main thread, so that the dialog can be dismissed while the rendering thread continues rendering/drawing. Even more complicated is that, for a simple dialog to show up while the background rendering is drawing objects onto the screen is hard. I do not understand how to link one part of the game logic to the next one.

Source:

This entire project took months to try to get this dialog to work, yet failed. I recreated only the problems at hand and scaled it down very much, so that the same error occurs at a suitable and manageable size. Below is the rewritten work, everything else (artwork, objects, classes, etc.) is removed, so as to concentrate on the core problem.


package ff.ff;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Surface.OutOfResourcesException;
public class Basic extends Activity {
private Render view;

public class Render extends SurfaceView implements Runnable {


//TODO: Test if AlertDialog can be able to work while another
//thread is running continuously.
//
// Failed miserably.

//ERROR Received:
/*
* 07-08 17:34:51.035: E/AndroidRuntime(7356): FATAL EXCEPTION: Thread-12
* 07-08 17:34:51.035: E/AndroidRuntime(7356): java.lang.RuntimeException: Main thread not allowed to quit
* 07-08 17:34:51.035: E/AndroidRuntime(7356): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:191)
* 07-08 17:34:51.035: E/AndroidRuntime(7356): at android.os.Looper.quit(Looper.java:231)
* 07-08 17:34:51.035: E/AndroidRuntime(7356): at ff.ff.Basic$Render$1$1.run(Basic.java:45)
* 07-08 17:34:51.035: E/AndroidRuntime(7356): at java.lang.Thread.run(Thread.java:1027)
*
*/


private int r, g, b;

private boolean running;
private SurfaceHolder holder;
private AlertDialog.Builder builder;
private AlertDialog dialog;

public Render(Context context) {
super(context);
holder = this.getHolder();
r = g = b = 0;
builder = new AlertDialog.Builder(context);
builder.setTitle("Enter");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Render Dialog", "Working...");
Log.d("Render Dialog", "Exiting the Looper loop...");
new Thread(new Runnable(){
public void run(){
Looper.getMainLooper().quit(); //<---------------------- ERROR WHERE IT OCCURRED.
}
}).start();
}
});
dialog = builder.create();
}

public void setLoopFlag(boolean value) {
running = value;
}

public void run() {
boolean flag = false;
while(running) {
if (holder.getSurface().isValid()) {
Canvas c = null;
try {
c = holder.getSurface().lockCanvas(null);
}
catch(IllegalArgumentException e) {
e.printStackTrace();
}
catch(OutOfResourcesException e) {
e.printStackTrace();
}
c.drawARGB(255, r, g, b);
r++;
g++;
b++;
if (r > 250 || g > 250 || b > 250) {
r = 0;
g = 0;
b = 0;
}
if (!flag){
flag = true;
Looper.prepare();
dialog.show();
Looper.loop();
}
holder.getSurface().unlockCanvasAndPost(c);
}
}
}
}


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new Render(this);
view.setLoopFlag(true);
setContentView(view);
Thread thread = new Thread(view);
thread.setName("Render Thread");
thread.start();
}
}


Neatly packed into 1 Java class.

Conclusion:

I hoped this is enough information needed to solve this matter.
Advertisement
hmm. I'll be honest I'm not sure what you are doing there. Your onclick event doesn't make a lot of sense to me, also I'm not sure why you are creating an instance of the class in its onCreate method.

If what you want to do is create a dialog that for example asks the player if they want Save on exit. Then I'd expect something like this:


AlertDialog.Builder builder = new AlertDialog.Builder(GameRender.this);
builder.setMessage("Save?").setCancelable(false).setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
AccountUpdater.updateAccount(getContentResolver(), _gameState);

GameRender.this.finish();
}
}).setNegativeButton("No", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
GameRender.this.finish();
}
}).show();


you can control the pausing and resuming of the render thread through the relevant activity life cycle methods onPause() and onResume().

Sorry if I missed the point.
I do believed that the point is missed. I now couldn't rephrase my question, so that the point is hard to miss... Because I don't know how to rephrase it.

No one ever thought of showing a dialog while the background is drawing objects at the same time?

Here's a mockup:

Screen background changes color while dialog stays popped up.
device-2012-07-09-221832.pngdevice-2012-07-09-221819.png
Okay I understand what you are looking for now. Ya that's perfectly doable. I knocked together a quick example based on an adaptation i did to one the sample projects.

TravelRenderThread.java


package njd.rougetrader;
import njd.FalseProphet.EntityFramework.Entity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.view.SurfaceHolder;
public class TravelRenderThread extends Thread
{
// private Bitmap _backgroundImage;
private int _canvasHeight = 1;
private int _canvasWidth = 1;
private SurfaceHolder _surfaceHolder;
private boolean _active;
Paint _lineColor; // = new Paint();
Paint _backColor; // = new Paint();
int _radius = 10;
int _red = 0;

boolean _isRed = true;

public TravelRenderThread(SurfaceHolder surfaceHolder, Context context,
Handler handler)
{
_surfaceHolder = surfaceHolder;
}

public void setRunning(boolean b)
{
_active = b;
}

/* Callback invoked when the surface dimensions change. */
public void setSurfaceSize(int width, int height)
{
// synchronized to make sure these all change atomically
synchronized (_surfaceHolder)
{
_canvasWidth = width;
_canvasHeight = height;

// don't forget to resize the background image
// _backgroundImage =
// _backgroundImage.createScaledBitmap(_backgroundImage, width,
// height, true);
}
}

@Override
public void run()
{
while (_active)
{
Canvas canvas = null;
try
{
canvas = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder)
{
render(canvas);
}
}
finally
{
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (canvas != null)
{
_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
_red++;
if (_red > 255)
_red = 0;
}
}

private void render(Canvas canvas)
{
// Draw the background image. Operations on the Canvas accumulate
// so this is like clearing the screen.

_lineColor = new Paint();
_lineColor.setAntiAlias(true);

if (_isRed)
_lineColor.setColor(Color.rgb(_red, 0, 255 - _red));
else
_lineColor.setColor(Color.rgb(0, _red, 255 - _red));

_backColor = new Paint();
_backColor.setAntiAlias(true);
_backColor.setColor(Color.BLACK);

canvas.drawRect(0, 0, _canvasWidth, _canvasHeight, _lineColor);
canvas.drawText(String.valueOf(_red), 100, 100, _backColor);

canvas.save();
canvas.restore();
}

public void setIsRed()
{
_isRed = true;

}

public void setIsGreen()
{
_isRed = false;
}
}


TravelView.java

package njd.rougetrader;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class TravelView extends SurfaceView implements SurfaceHolder.Callback
{

TravelRenderThread _thread;

public TravelRenderThread getThread()
{
return _thread;
}

public TravelView(Context context, AttributeSet attrs)
{
super(context, attrs);
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);

// create thread only; it's started in surfaceCreated()
_thread = new TravelRenderThread(holder, context, new Handler());

setFocusable(true); // make sure we get key events
}

public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height)
{
_thread.setSurfaceSize(width, height);
}

public void surfaceCreated(SurfaceHolder holder)
{
_thread.setRunning(true);
_thread.start();

}

public void surfaceDestroyed(SurfaceHolder holder)
{
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
_thread.setRunning(false);
while (retry)
{
try
{
_thread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}

public void setIsRed()
{
_thread.setIsRed();

}

public void setIsGreen()
{
_thread.setIsGreen();

}
}


TravelActivity.java


package njd.rougetrader;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
public class TravelActivity extends Activity
{

TravelView _view;
TravelRenderThread _thread;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.travel_activity);
_view = (TravelView) findViewById(R.id.Render_Canvas);
_thread = _view.getThread();

}

public void openDialog(View view)
{
AlertDialog.Builder builder = new AlertDialog.Builder(TravelActivity.this);
builder.setMessage("Red or Green?").setCancelable(false).setPositiveButton("Red", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
_view.setIsRed();

}
}).setNegativeButton("Green", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
_view.setIsGreen();
}
}).show();
}

}
Ah! There's a small bit I just figured out, thanks to your hint.

Here's my actual program, please test.


package ff.ff;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Basic extends Activity {
private AlertDialog dialog;
private AlertDialog.Builder builder;
private BackgroundColors view;

public class BackgroundColors extends SurfaceView implements Runnable {
private Thread thread;
private boolean running;
private SurfaceHolder holder;

public BackgroundColors(Context context) {
super(context);
}

public void run() {
int r = 0;
while (running){
if (holder.getSurface().isValid()){
Canvas canvas = holder.lockCanvas();
if (r > 250)
r = 0;
r += 10;
try {
Thread.sleep(300);
}
catch(InterruptedException e) {
e.printStackTrace();
}
canvas.drawARGB(255, r, 255, 255);
holder.unlockCanvasAndPost(canvas);
}
}
}

public void start() {
running = true;
thread = new Thread(this);
holder = this.getHolder();
thread.start();
}

public void stop() {
running = false;
boolean retry = true;
while (retry){
try {
thread.join();
retry = false;
}
catch(InterruptedException e) {
retry = true;
}
}
}

public boolean onTouchEvent(MotionEvent e){
dialog.show();
return false;
}
}

public void onCreate(Bundle b) {
super.onCreate(b);
view = new BackgroundColors(this);
this.setContentView(view);
builder = new AlertDialog.Builder(this);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Basic", "It worked");
}
});
dialog = builder.create();
}

public void onPause(){
super.onPause();
view.stop();
}

public void onResume(){
super.onResume();
view.start();
}
}

This topic is closed to new replies.

Advertisement