Thursday, January 16, 2014

Libgdx Scene2d Dialog resize and style

Having the need to quickly generate dialogs in my game, I looked into the Dialog class in the scene2d.ui package. It allows you to create dialogs with a title, text, and all the buttons you need.
The Dialog object contains a "contentTable" field for its inner text, and a "buttonTable" field where the buttons are placed. Both are Tables. The Dialog class extends Window, which itself is a Table. So you can fine-tune sizes and paddings of the various elements.

Different story for the entire dialog sizing; if you simply use the setSize(w, h) method it won't affect anything. This is because Dialog.show() method calls WidgetGroup.pack(), which overrides any size is explicitly set, and dynamically resizes the group depending on its content. To fix this, you can simply override getPrefWidth() and getPrefHeight() to return the values you need.

I created this class for my needs, which automatically styles the text and buttons you add to the dialog.

 public class CustomDialog extends Dialog {  
   
     public CustomDialog (String title) {  
         super(title, Assets.skin);  
         initialize();  
     }  
       
     private void initialize() {  
         padTop(60); // set padding on top of the dialog title  
         getButtonTable().defaults().height(60); // set buttons height  
         setModal(true);  
         setMovable(false);  
         setResizable(false);  
     }  
   
     @Override  
     public CustomDialog text(String text) {  
         super.text(new Label(text, Assets.skin, "medium-green"));  
         return this;  
     }  
       
     /**  
      * Adds a text button to the button table.  
      * @param listener the input listener that will be attached to the button.  
      */  
     public CustomDialog button(String buttonText, InputListener listener) {  
         TextButton button = new TextButton(buttonText, Assets.skin);  
         button.addListener(listener);  
         button(button);  
         return this;  
     }  
   
     @Override  
     public float getPrefWidth() {  
         // force dialog width  
         return 480f;  
     }  
   
     @Override  
     public float getPrefHeight() {  
         // force dialog height  
         return 240f;  
     }  
 }  

You also need to declare a WindowStyle declaration in your skin.json file.
For more informations about skins, take a look at my Skin tutorial.

 com.badlogic.gdx.scenes.scene2d.ui.Window$WindowStyle: {  
     default: { background: button-disabled, titleFont: fx35, titleFontColor: red }  
 }  

Example usage in your game screen:

 new CustomDialog("Exit game") // this is the dialog title  
     .text("Leave the game?") // text appearing in the dialog  
     .button("EXIT", new InputListener() { // button to exit app  
         public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {  
             Gdx.app.exit();  
             return false;  
         }  
     })  
     .button("Keep playing") // button that simply closes the dialog  
     .show(stage); // actually show the dialog  

Output:


Wednesday, January 15, 2014

Candy Falls! LWP goes free

You can now grab our Candy Falls! Live Wallpaper for Android for free.

Old price: 0.99$
New price: FREE


Remember to shake your phone to get even more sweets falling!

Google Play link:
https://play.google.com/store/apps/details?id=com.pimentoso.android.candyfall.lwp

Sunday, December 1, 2013

Retrieving data from a JSON webservice in Android - simple tutorial

In this guide I'll show how to write a basic Android app that retrieves data from a JSON webservice, and simply shows it in a list.

We'll be using the following stuff.

Tutorial

Create a new Android project, with a MainActivity class inside. Create a "libs" folder inside the project, and put the GSON jar in there. When you refresh the project, Eclipse should automatically add the jar to the project's build path.

Let's have our activity extend ListActivity instead of Activity.

 public class MainActivity extends ListActivity {  
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
      }  

Now let's look at the "citiesJSON" webservice API.

Webservice Type : REST 
Url : api.geonames.org/citiesJSON?
Parameters : 
north,south,east,west : coordinates of bounding box 
callback : name of javascript function (optional parameter) 
lang : language of placenames and wikipedia urls (default = en)
maxRows : maximal number of rows returned (default = 10)

Result : returns a list of cities and placenames in the bounding box, ordered by relevancy (capital/population). Placenames close together are filterered out and only the larger name is included in the resulting list.

We'll call the webservice with the 4 coordinates parameters. The webservice also requires an "username" parameter. You can use the "pimentoso" user for this test, or you can register your own username here: http://www.geonames.org/login

Go to the JSONgen website, and paste the sample URL for the webservice.


When you hit "generate", the website will have you download a zip file containing the generated classes. Unzip the file and copy the classes in your project. The classes will look like this

 package com.example.placesjson;  
 import java.util.List;  
 public class GeonameList {  
      private List<Geonames> geonames;  
      public List<Geonames> getGeonames() {  
           return this.geonames;  
      }  
      public void setGeonames(List<Geonames> geonames) {  
           this.geonames = geonames;  
      }  
 }  

 package com.example.placesjson;  
 public class Geonames {  
      private String countrycode;  
      private String fcl;  
      private String fclName;  
      private String fcode;  
      private String fcodeName;  
      private Number geonameId;  
      private Number lat;  
      private Number lng;  
      private String name;  
      private Number population;  
      private String toponymName;  
      private String wikipedia;  
      [... getters and setters]  

Now we're gonna work on the activity. We're going to create the "callService()" method that does the main job: starting a thread that calls the webservice, and deserialize the data. The comments in the code should be self-explanatory.

 private void callService() {  
      // Show a loading dialog.  
      dialog = ProgressDialog.show(this, "Loading", "Calling GeoNames web service...", true, false);  
      // Create the thread that calls the webservice.  
      Thread loader = new Thread() {  
           public void run() {  
                // init stuff.  
                Looper.prepare();  
                cities = new GeonameList();  
                boolean error = false;  
                // build the webservice URL from parameters.  
                String wsUrl = "http://api.geonames.org/citiesJSON?lang=en&username=pimentoso";  
                wsUrl += "&north="+COORD_N;  
                wsUrl += "&south="+COORD_S;  
                wsUrl += "&east="+COORD_E;  
                wsUrl += "&west="+COORD_W;  
                String wsResponse = "";  
                try {  
                     // call the service via HTTP.  
                     wsResponse = readStringFromUrl(wsUrl);  
                     // deserialize the JSON response to the cities objects.  
                     cities = new Gson().fromJson(wsResponse, GeonameList.class);  
                }  
                catch (IOException e) {  
                     // IO exception  
                     Log.e(TAG, e.getMessage(), e);  
                     error = true;  
                }  
                catch (IllegalStateException ise) {  
                     // Illegal state: maybe the service returned an empty string.  
                     Log.e(TAG, ise.getMessage(), ise);  
                     error = true;  
                }  
                catch (JsonSyntaxException jse) {  
                     // JSON syntax is wrong. This could be quite bad.  
                     Log.e(TAG, jse.getMessage(), jse);  
                     error = true;  
                }  
                if (error) {  
                     // error: notify the error to the handler.  
                     handler.sendEmptyMessage(CODE_ERROR);  
                }  
                else {  
                     // everything ok: tell the handler to show cities list.  
                     handler.sendEmptyMessage(CODE_OK);  
                }  
           }  
      };  
      // start the thread.  
      loader.start();  
 }  

The code contains the "readStringFromUrl()" utility method which is not covered in this guide. Please download the project zip at the end of this post to grab the code.

When the thread has completed, the "cities" object shoud contain data returned from the webservice.


This is the simple handler that's called at the end of the method.

 // This handler will be notified when the service has responded.  
 final Handler handler = new Handler() {  
      public void handleMessage(Message msg) {  
           dialog.dismiss();  
           if (msg.what == CODE_ERROR) {  
                Toast.makeText(MainActivity.this, "Service error.", Toast.LENGTH_SHORT).show();  
           }  
           else if (cities != null && cities.getGeonames() != null) {  
                Log.i(TAG, "Cities found: " + cities.getGeonames().size());  
                buildList();  
           }  
      }  
 };  

The last thing that remains is to actually populate the list, so let's write the "buildList()" method.

 private void buildList() {  
      // init stuff.  
      List<Map<String, String>> data = new ArrayList<Map<String, String>>();  
      Map<String, String> currentChildMap = null;  
      String line1;  
      String line2;  
      // cycle on the cities and create list entries.  
      for (Geonames city : cities.getGeonames()) {  
           currentChildMap = new HashMap<String, String>();  
           data.add(currentChildMap);  
           line1 = city.getToponymName() + " (" + city.getCountrycode() + ")";  
           line2 = "Population: " + city.getPopulation();  
           currentChildMap.put("LABEL", line1);  
           currentChildMap.put("TEXT", line2);  
      }  
      // create the list adapter from the created map.  
      adapter = new SimpleAdapter(this, data, android.R.layout.simple_list_item_2,   
                new String[] { "LABEL", "TEXT" },  
                new int[] { android.R.id.text1, android.R.id.text2 });  
      setListAdapter(adapter);  
 }  

Let's wrap it all up, by calling "callService()" when the activity is started.

 @Override  
 protected void onCreate(Bundle savedInstanceState) {  
      super.onCreate(savedInstanceState);  
      callService();  
 }  

The final result should be something like this.



Error handling

You will notice that when the webservice returns an error, a JsonSyntaxException is not actually thrown. This is because GSON only throws the exception if your class has a field whose type didn't match what is in the JSON. So, in case of an error like this-

{"status":{"message":"user does not exist.","value":10}}

The exception is not actually thrown. So if you want to retrieve the error message, you could expand your GeonameList class to contain a Status object, so GSON can fill it when it is returned. 
You can read more about it here-

Other libraries

If GSON is a bit too heavy for you (with its almost 200KB) you can look into JSONbeans, a lighter library by EsotericSoftware.
https://github.com/EsotericSoftware/jsonbeans

Download

You can get the project on Github.
https://github.com/Pimentoso/AndroidPlacesJson

Alternatively, you can download the Eclipse project ZIP here.
http://www.pimentoso.com/uploads/PlacesFromJson.zip

Friday, November 8, 2013

New Facebook page

We created a new page for Pimentoso on Facebook!


On this page we will post updates, releases and graphics of all our apps.
Please follow us there too if you were a fan of the Candy Falls page!

Monday, September 16, 2013

Custom color chooser for Hexagons LWP

Our Evangelion-themed live wallpaper gets a new option: you can now create a custom color theme, choosing the colors for NORMAL, WARNING and EMERGENCY state of the hexagons. A color wheel lets you select hue and there's a slider to adjust lightness.


Thanks to Lars Werkman and Marie Schweiz for providing this awesome open-source holo color picker.

As usual, you can grab the app on Google Play at this link.

Thursday, July 25, 2013

MiSoundRecorder graphic fixes for Nexus 4

Maybe some of you will know MiSoundRecorder, a neat sound recorder app ported from MIUI rom by the XDA user HootanParsa. It records in MP3 and OGG formats.

The project is stalled and has gone open source, so I grabbed the sources and did a couple layout fixes so that everything is aligned correctly on Nexus 4 (and hopefully all other xhdpi phones).

Any credit for the app goes to their original creators.

Get the APK here. It's compiled for Android 2.3.3+
http://www.pimentoso.com/uploads/MiSoundRecorder.1.4.apk

Screenshot from Nexus 4:


Original XDA thread:
http://forum.xda-developers.com/showthread.php?t=1752011

Saturday, July 20, 2013

Mobile Data On/Off Widget

I recently got myself a Nexus 4, and I started looking for some simple on/off widgets to compensate the lack of features of the stock 4.2 Power Control widget (which is actually unchanged since 2.2). I came from Cyanogenmod so I was used to having all the toggles I could ever need.

I was surprised that on Google Play there were no widgets made to resemble the stock Power Control widget. In particular, I was looking for a mobile data switch on/off. Working in front of a pc all day, I usually turn off mobile data when I don't need it, to save battery.

So I tried myself with this simple widget and I styled it to look neat near the holo Power Control widget. Check the screenshot below for comparison.



The widget is only a few KB, with no ads, no settings app, no icons in app drawer, only pure and simple widget. You can grab it on Google Play at this link.