Home » Android » android – libGDX Alert Dialog

android – libGDX Alert Dialog

Posted by: admin June 15, 2020 Leave a comment

Questions:

I have used code below:

 AlertDialog.Builder bld;

 if (android.os.Build.VERSION.SDK_INT <= 10) {
     //With default theme looks perfect:
     bld = new AlertDialog.Builder(AndroidLauncher.this);
 } else {
     //With Holo theme appears the double Dialog:
     bld = new AlertDialog.Builder(AndroidLauncher.this, android.R.style.Theme_Holo_Dialog_MinWidth);
 }

 bld.setIcon(R.drawable.ic_launcher);
 bld.setTitle("Exit");
 bld.setMessage("Are you sure you want to exit?");
 bld.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
     @Override
     public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); }
 });
 bld.setPositiveButton("Exit", new DialogInterface.OnClickListener() {
     @Override
     public void onClick(DialogInterface dialog, int which) { finish(); }
 });
 bld.setCancelable(false);
 bld.create().show();

It seems fine, but it says “import android.app.AlertDialog cannot resolve”.
It is a standard libGDX project in Android Studio.

How to&Answers:

In libgdx you should use a scene2d dialog instead of the native Android DialogInterface. Below is how you would add a completely skinned dialog to the stage in libgdx with custom button images and background image. You would just need to substitute your own background and button image textures and font, then call quitGameConfirm() when you’re ready to display the dialog…

import com.badlogic.gdx.scenes.scene2d.ui.Dialog;

public void quitGameConfirm() {

    LabelStyle style = new LabelStyle(_fontChat, Color.WHITE);
    Label label1 = new Label("Are you sure that you want to exit?", style);
    label1.setAlignment(Align.center);
    //style.font.setScale(1, -1);
    style.fontColor = Color.WHITE;

    Skin tileSkin = new Skin();
    Texture tex = new Texture(myButtontexture);
    tex.setFilter(TextureFilter.Linear, TextureFilter.Linear);
    tileSkin.add("white", tex);
    tileSkin.add("default", new BitmapFont());

    TextButton.TextButtonStyle textButtonStyle = new TextButton.TextButtonStyle();
    textButtonStyle.up = tileSkin.newDrawable("white");
    textButtonStyle.down = tileSkin.newDrawable("white", Color.DARK_GRAY);
    textButtonStyle.checked = tileSkin.newDrawable("white",
            Color.LIGHT_GRAY);
    textButtonStyle.over = tileSkin.newDrawable("white", Color.LIGHT_GRAY);
    textButtonStyle.font = _myTextBitmapFont;
    textButtonStyle.font.setScale(1, -1);
    textButtonStyle.fontColor = Color.WHITE;
    tileSkin.add("default", textButtonStyle);

    TextButton btnYes = new TextButton("Exit", tileSkin);
    TextButton btnNo = new TextButton("Cancel", tileSkin);

    // /////////////////
    Skin skinDialog = new Skin(Gdx.files.internal("data/uiskin.json"));
    final Dialog dialog = new Dialog("", skinDialog) {
        @Override
        public float getPrefWidth() {
            // force dialog width
            // return Gdx.graphics.getWidth() / 2;
            return 700f;
        }

        @Override
        public float getPrefHeight() {
            // force dialog height
            // return Gdx.graphics.getWidth() / 2;
            return 400f;
        }
    };
    dialog.setModal(true);
    dialog.setMovable(false);
    dialog.setResizable(false);

    btnYes.addListener(new InputListener() {
        @Override
        public boolean touchDown(InputEvent event, float x, float y,
                int pointer, int button) {

            // Do whatever here for exit button

            _parent.changeState("StateMenu");
            dialog.hide();
            dialog.cancel();
            dialog.remove();                

            return true;
        }

    });

    btnNo.addListener(new InputListener() {
        @Override
        public boolean touchDown(InputEvent event, float x, float y,
                int pointer, int button) {

            //Do whatever here for cancel

            dialog.cancel();
            dialog.hide();

            return true;
        }

    });

    TextureRegion myTex = new TextureRegion(_dialogBackgroundTextureRegion);
    myTex.flip(false, true);
    myTex.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
    Drawable drawable = new TextureRegionDrawable(myTex);
    dialog.setBackground(drawable);

    float btnSize = 80f;
    Table t = new Table();
    // t.debug();

    dialog.getContentTable().add(label1).padTop(40f);

    t.add(btnYes).width(btnSize).height(btnSize);
    t.add(btnNo).width(btnSize).height(btnSize);

    dialog.getButtonTable().add(t).center().padBottom(80f);
    dialog.show(stage).setPosition(
            (MyGame.VIRTUAL_WIDTH / 2) - (720 / 2),
            (MyGame.VIRTUAL_HEIGHT) - (MyGame.VIRTUAL_HEIGHT - 40));

    dialog.setName("quitDialog");
    stage.addActor(dialog);

}

enter image description here

Answer:

The problem is that you are trying to create an Android widget which I suspect you are doing it in the Libgdx-core implementation. The core implementation does not have any references to Android SDK.

That is because it is the Android project which inherits the core project. As a result the core project is not aware of any dependencies loaded to the Android implementation.

To overcome this you need to create an interface between Android project and Core Project. That will allow you to call methods inside the Android Project.
The interface must be created inside the core Project in order for both projects to have access to it.

For example you create CrossPlatformInterface.java inside core Project. But first let’s create a callback to get feedback from the Ui Thread inside the Libgdx Thread. It is important to remember that Libgdx has a seperate thread that Android main thread!!! If you try to run Widgets of Android from Libgdx threads the Application will crush.

Let’s make the callback for the AlertDialog. I will suggest an Abstract class here in order to be able to override only the methods you want because sometimes Alertdialog can have 1,2 or 3 buttons.

In Core Project create AlertDialogCallback.java:

public abstract class AlertDialogCallback{

    public abstract void positiveButtonPressed();
    public void negativeButtonPressed(){}; // This will not be required
    public void cancelled(){}; // This will not be required

}

In Core Project also create CrossPlatformInterface.java:

public interface CrossPlatformInterface{
    public void showAlertDialog(AlertDialogCallback callback);
}

You notice that in the showAlertDialog method we pass the callback to get feedback when buttons are pressed!

Then you create a Class inside Android project that will implement the CrossPlatformInterface like:

public ClassInsideAndroidProject implements CrossPlatFormInterface{

   private AndroidLauncher mActivity; // This is the main android activity

   public ClassInsideAndroidProject(AndroidLauncher mActivity){
        this.mActivity = mActivity;
   }
   public void showAlertDialog(final AlertDialogCallback callback){

      mainActivity.runOnUiThread(new Runnable(){

        @Override
        public void run() {

            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
            builder.setTitle("Test");
            builder.setMessage("Testing");
            builder.setPositiveButton("OKAY", new OnClickListener(){

                @Override
                public void onClick(DialogInterface dialog, int which) {

                    callback.positiveButtonPressed();

                }   
            });
            builder.setNegativeButton(negativeButtonString, new OnClickListener(){

                @Override
                public void onClick(DialogInterface dialog, int which) {

                    callback.negativeButtonPressed();

                }

            });

            AlertDialog dialog = builder.create();
            dialog.show();
        }
    });
   }
}

Important notes

  1. The CrossPlatformInterface will be instantiated inside the MainActivity (AndroidLauncher) as you will see below.
  2. The AlertDialog will be created inside the android UI thread. Because we are coming from the Libgdx thread to create the AlertDialog we need to use runOnUiThread to ensure the AlertDialog is created in ui thread.

Finally how to execute this:

Instantiate CrossPlatform interface inside Android main Activity and pass the Activity to the Interface instance which is passed inside the MyGdxGame:

public class MainActivity extends AndroidApplication {

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
            cfg.useGL20 = false;

    initialize(new MyGdxGame(new ClassInsideAndroidProject(this)), cfg);
    }
}

Finally when the MyGDxGame is created we get the instance of the crossplatform interface and we can the call any functions we want to the android ui thread.

public class MyGdxGame extends Game {

ClassInsideAndroidProject crossPlatformInterface;

public MyGdxGame(ClassInsideAndroidProject crossPlatformInterface){
     this.crossPlatformInterface = crossPlatformInterface;
}

@Override
public void create() {

    crossPlatformInterface.showAlertDialog(new AlertDialogCallback(){

       @Override
       public void positiveButtonPressed(){

       //IMPORTANT TO RUN inside this method the callback from the ui thread because we want everything now to run on libgdx thread! this method ensures that.
          Gdx.app.postRunnable(new Runnable().....) 

       }
       @Override
       public void negativeButtonPressed(){

       }; // This will not be required
       @Override
       public void cancelled(){

        }; // This will not be required
    });
}

@Override
public void render() {
    super.render();
}

public void dispose() {
    super.dispose();
}

public void pause() {
    super.pause();
}
}

I think it was much more writing I first intended. It might look daunting but actually is fairly simple. Well after you’ve done it everything looks simpler :).
The advantage of this effort is after you make this interface any call to android widget will be very easy and thread safe.

Hope it gives a good picture.

Answer:

This works (tested). Simply pass in the FragmentActivity or Activity
via your game constructor. You have to pass something in (like
ClassInsideAndroidProject). Why not pass in a really useful element !.

//---------------------------------------------------------------------------
        /**  INSIDE the libgdc core, create a custom NATIVE android dialog
         * :- breaks the rules somewhat for the core,
         *  but if you ONLY using Android, why not use android Native!
         *   @member_var  private final FragmentActivity m_fa; 
         * @constructor public xx_your_app_xx(FragmentActivity m_fa) 
         *{
         *  this.m_fa = m_fa;
         *}
         *  @called_with if(m_fa != null) showCustomDialog(m_fa);
         * @param fa
         */
        public static void showCustomDialog(final FragmentActivity fa) //or Activity 
        {
            fa.runOnUiThread(new Runnable()
            {
    //          boolean[] info;
                @Override
                public void run()
                {
                    LinearLayout ll_Main     = new LinearLayout(fa);
                    LinearLayout ll_Row01    = new LinearLayout(fa);
                    LinearLayout ll_Row02    = new LinearLayout(fa);
                    LinearLayout ll_Row09    = new LinearLayout(fa);
                    LinearLayout ll_Row10    = new LinearLayout(fa);

                    ll_Main.setOrientation(LinearLayout.VERTICAL);
                    ll_Row01.setOrientation(LinearLayout.HORIZONTAL);
                    ll_Row02.setOrientation(LinearLayout.HORIZONTAL);
                    ll_Row09.setOrientation(LinearLayout.HORIZONTAL);
                    ll_Row10.setOrientation(LinearLayout.HORIZONTAL);

                    final CheckBox checkBox  = new CheckBox(fa);
                    final CheckBox cb_debug  = new CheckBox(fa);
                    final EditText et_User   = new EditText(fa);
                    final EditText et_Pass   = new EditText(fa);

                    TextView tv_Check        = new TextView(fa);
                    TextView tv_Debug        = new TextView(fa);
                    TextView tv_User         = new TextView(fa);
                    TextView tv_Pass         = new TextView(fa);

                    tv_Check.setText("rotation lock: ");
                    tv_Debug.setText("debug: ");
                    tv_User.setText("Username: ");
                    tv_Pass.setText("Password: ");

                    ll_Row01.addView(tv_Check);
                    ll_Row01.addView(checkBox);

                    ll_Row02.addView(tv_Debug);
                    ll_Row02.addView(cb_debug);

                    ll_Row09.addView(tv_User);
                    ll_Row09.addView(et_User);

                    ll_Row10.addView(tv_Pass);
                    ll_Row10.addView(et_Pass);

                    ll_Main.addView(ll_Row01);
                    ll_Main.addView(ll_Row02);
    //              ll_Main.addView(ll_Row09);
    //              ll_Main.addView(ll_Row10);

                    AlertDialog.Builder alert = new AlertDialog.Builder(fa);//this.getActivity()
                    alert.setTitle("Camera settings");
                    alert.setView(ll_Main);
                    alert.setCancelable(false);
                    alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() 
                    {
                        @Override
                        public void onClick(DialogInterface dialog, int which) 
                        {
    //                      info1[0] = checkBox.isChecked();
    //                      info1[1] = cb_debug.isChecked();
    //                      String user = et_User.getText().toString();
    //                      String pass = et_Pass.getText().toString();
                            //do something with the data
                            Gdx.app.log("INFO", "**** positiveButtonPressed works here too! ***");
                            Toast.makeText(fa,
                                    "checkBox: " + checkBox.isChecked() +
                                    ", cb_debug: " + cb_debug.isChecked(),
                                    Toast.LENGTH_LONG).show();
                            //IMPORTANT TO RUN inside this {} means everything now  run's on libgdx thread!.
                            Gdx.app.postRunnable( new Runnable() 
                               {
                                    public void run() 
                                    {
                                        //do something with the data
                                        Gdx.app.log("INFO", "**** positiveButtonPressed works here ****");
                                    }//run
                                });//postRunnable
                        }//onClick
                    });//setPositiveButton
                    alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() 
                    {   
                        @Override
                        public void onClick(DialogInterface dialog, int which) 
                        {
                            dialog.dismiss();
                        }//setPositiveButton
                    });//setNegativeButton
                    AlertDialog dialog = alert.create();
                    dialog.show();
                }//run
            });//runOnUiThread
        }//showCustomDialog
    //--------------------------------------------------------------------------------