Wednesday, July 3, 2013

Integrating Twitter 1.1 api in Android with oauth including app-only authentication

Introduction

One major change is that all API calls now have to be authenticated (and authorized) via OAuth: "user authorization (and access tokens) are required for all API 1.1 requests". That means any action on Twitter that is related to a user has to happen this way.

Luckily there is also a so called application-only (app-only) authentication for actions that don't require a user as a context. Those are requests on behalf of an application. Think about search and getting a timeline for a user. One thing that's definitely different from the other calls is that this app-only authentication has to use OAuth 2.0, while the user-context based authentication has to use OAuth 1.0a!

In this post I'll describe how to implement both, including a full working Android code example.




App-only Twitter 1.1 Authentication OAuth 2.0

Information about this principle can be found here. As said above, you can use it for accessing the Twitter API 1.1 endpoints that do not require a user action. For example searching for tweets falls in this category; just like getting a tweet from a user's timeline. Tweeting a status update does not fall in this category, because a user has to give permission for this. You can see if a Twitter API call supports application-only authentication if there's a rate-limit specified with it. E.g for the search it says on the right: 450/app. So that's accessible via the app-only API. But getting a user's direct messages isn't, it only has 15/user specified.

The problem with this userless-context API solution I had was my app was using the Signpost library for the Twitter 1.0 API. I preferably would have used that lib for this app-only authentication too, but the Signpost still does not support OAuth 2 yet...
Then I tried to use the Scribe library, which supports OAuth 2.0, but I couldn't get that working quickly. 
Therefore I went back to the basics and used plain HTTP POST and GET methods, using this blogpost as a starting point. All pieces to get it working are in that post, but not available as one complete code set, that's why I made this post. Plus you might come along a few problems too... also described below.

Making it work

Easiest is to refer to the above mentioned Crazy Bob's blogpost to get started and what's happening why.
In my attached example code (see bottom of this post) most of the logic occurs in OAuthTwitterClient.java. In OnCreate() the UI is set up, including the consumer, provider and client for the OAuth 1.0a using Signpost (see next section for details on that part).

When a user clicks on the top button of the application, named '1a) Request bearer-token via app-only oauth2') the getBearerTokenOnClickListener.onClick() method is invoked. There you see the bearer token getting retrieved via requestBearerToken(), as described in Crazy Bob's blogpost.
That should give the bearer token which you need as mentioned here
Then to get a tweet from a Twitter user (in the code I'm using 'twitterapi'), click on the second button named '1b) Get tweet from user via app-only oauth2'. That invokes its listener which invokes fetchTimelineTweet(), which in turn gets the tweet using the bearer token, after which the tweet is displayed on the screen.

Troubleshooting 

I ran into a few issues:
  1. When fetching the tweet, the getInputStream (in readResponse()) was giving a FileNotFoundException. Turns out that since Android 4 ICS (Ice Cream Sandwich), this can occur because the conn.setDoOutput(true) causes the request to be turned into a POST even though the specified request method is GET! See here. The fix is to remove that line.
  2. When invoking requestBearerToken() two times in a row on Android 1.5, the responseCode showed as -1 sometimes; mainly when waiting about 30 seconds before invoking it again. That turned out to be a known issue for Android before 2.2 Froyo. For that the method disableConnectionReuseIfNecessary() is put in place. 
  3. It seems a bearer token lasts "forever", until it is revoked/invalidated. Twitter can do that but you can do it yourself too, via the invalidate_token API call.
PS 1: no the code is not optimal, for example the error handling can be done a lot better
PS 2: and no the UI won't win any "Best Interface" prizes
PS 3: yes all network calls should be done on a non-UI thread really. Yup that's some homework for you too :)


User Context Twitter 1.1 Authentication OAuth 1.0a

For updating a user's status (i.e. tweeting), you have to be authenticated and the user has to have authorized you to perform those type of actions. Usually the user authorization happens by switching to a browser in which the user has to log in to Twitter and say whether the application is allowed to perform the requested actions.
Luckily, for this type of authentication still the Signpost library can be used since its based on OAuth 1.0a.

Making it work

After upgrading to the signpost-core-1.2.1.2.jar and signpost-commonshttp4-1.2.1.2.jars, everything almost worked immediately. Especially make sure to use CommonsHttpOAuthProvider as it says on the Signpost homepage in the Android section.

Since my app still runs on Android SDK 1.5 (yes!) I tested the solution on a 1.5 emulator.
But then I ran into my first problem, authentication failed with: javax.net.ssl.SSLException: Not trusted server certificate.
To get this working, I had to make sure all the Twitter Root CA certificates are known. For that I used this blogpost. See the class CrazyBobHttpClient for the implementation. After putting in all VeriSign certificates one by one (search for the string 'class 3 Public Primary Certification Authority - G' in the .pem file mentioned in this Twitter Developers Forum post to find all 5 of them).
In short, issue the following command to have each one added to your own keystore:

C:\>C:\jdk1.7.0_17\jre\bin\keytool -import -v -trustcacerts -alias 3 -file verisign_cert_class3_g5.cert -keystore twitterkeystore.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-jdk16-145.jar -storepass ez24get

So in the above I'm adding the VeriSign G5 certificate. The -alias 3 you have to increase each time. And notice the password used: 'ez24get', you'll find that in the CrazyBobHttpClient class.

If you see:

Certificate already exists in system-wide CA keystore under alias
Do you still want to add it to your own keystore? [no]:  

answer with: yes

And do the same for the DigiCert certificates, you only need the 3 Root ones. You can find the separate certificates and keystore in the res/raw directory of the example application. The separate certificates don't have to be provided, I just left them there for reference.

After performing all these steps, I was able to authenticate and even tweet. Just as a test I switched back to the DefaultHttpClient(). And the authentication now goes fine using that implementation too! Strange, because AFAIK that one just uses the default system keystore, and before it gave the "not trusted server certificate error"... Strange to me. If anybody knows the reason why, please leave it in the comments!

PS: maybe even cleaner is to add your own created keystore to the existing keystore(s). See the comments at the top in the CrazyBobHttpClient.java.


Outstanding Issues

Well, issues.... more like questions: 
  • Why does the DefaultHttpClient() suddenly work (see previous section)?
  • It would be nice to have an example for app-only authentication using an OAuth 2.0 library like Scribe instead of using this low level POST/GET code.
  • In the LogCat I see these messages appear after each API call:

    07-01 18:59:03.575: W/ResponseProcessCookies(732): Invalid cookie header: "set-cookie: guest_id=v1%3A137270514572832152;  Domain=.twitter.com; Path=/; Expires=Wed, 01-Jul-2015 18:59:05 UTC". Unable to parse expires attribute: Wed, 01-Jul-2015 18:59:05 UTC
    Why do these appear?
  • It should be possible to get the Twitter Timeline for a given user using your application's private access tokens (and thus not requiring the app-only authentication). Of course this is not recommended, because that would mean you'd have to put these tokens in your application. But for some situations it could be an option. See this PHP code on how that can be done.


The Sample Code Application

You can find the complete source code here
It should run on anything from Android 1.5 and higher.
It has been tested by me on:
  • Android 1.5 emulator
  • Android 4.0 Samsung S2
  • Android 4.0 emulator
You might want to remove the scribe-oauth2.0-1.3.0.jar from the libs directory, just left it there for reference. Same goes for the certificates in the res/raw/ directory. Removal is not necessary, it just uses unnecssary storage.

How to run it

  1. Load the project in your favorite IDE or whatever you use to build Android applications.
  2. In OAuthTwitterClient change the values of CONSUMER_KEY and CONSUMER_SECRET to your own application keys.
  3. Also modify the CALLBACK_URL to your application's callback URL. Note: you can also make an OOB (out-of-bounds) call that doesn't require a callback URL, but that's for you to figure out, didn't spend time on that.
  4. Deploy it. You should see a screen like this:


  5. Press button 1a) to get a bearer token. After pressing it should look like:


  6. Press 1b) to get the tweet. That should look like:


  7. Press button 2a) to get authenticated and authorized to post a tweet on behalf of a user. When the browser opens, enter the credentials under which account the tweet should be performed (no screenshot for that one). Then authorize the app (click Authorize app):




    It should get back to:




    Note: I am getting a message that the certificate is not ok. This is I think because the quite old 1.5 emulator does not have that root certificate:




    Because the certificate looks fine: 
  8. Enter a tweet text.
  9. Press button 2b) to tweet the text above it. That should give you:


  10. At the bottom of the screen you can see some statuses of what's going on. If you see any error message, check the LogCat.
  11. That's it!
And here two examples of what it can look like after integration in an app, here in the free Android SongDNA app.
Showing all tweets with the words 'shakira' and 'underneath your clothes' in it.



And the different actions possible on each tweet:



Wednesday, April 17, 2013

Vaadin Charts add-on showing custom properties in tooltip hover

Recently I had to investigate if it is possible to show other data than the X and Y value when hovering over a stacked bar graph in the Vaadin Charts add-on. And yes it is possible!
Nowhere I could find an example on how to do this, so that's why I wrote this blogpost. Hope it helps some people :)

Setup:
- Vaadin 7.0.1
- Charts add-on 1.0.1. In version 1.0.0 you can't set the color of the stacked bars using PlotOptions and ContainerDataSeries. This version has a fix for that.
- Note: I had to manually modify some labels of the hover in the screenshot since I can't use the real data used in the project. Same goes for the code, did not check if it still compiles. Let me know if something essential is missing :) Oh yeah and this is all demo-code so no quality complaints please....

Goal:
- Be able to add a custom field to the hover. And even better: be able to add an array to the hover tooltip.

Here's a screenshot of what the goal was. Click on it for a larger image:



The code:
Notice the Grapes ok: 7 Total: 16 in the hover text. These are the standard x and y values you can show easily via:




        Tooltip tooltip = new Tooltip();
        tooltip.setFormatter("function() { return ''+ this.x +'"+ " '+ this.series.name +': '+ this.y 
+' '+'Total: '+ this.point.stackTotal;}");

        conf2.setTooltip(tooltip);



But also note the extra this.point.custom = 708. That's an example of adding a custom value. The tooltip code then looks like:




tooltip.setFormatter("function() {"return ''+ this.x +'"+ " '+this.series.name +': '+ this.y +' '+
'Total: '+ this.point.stackTotal + ' this.point.custom = ' + this.point.custom + "''"



And then below that line you see a set of table-like data: Quality 1: 2 Stored: 3 Interesting: 4. This is the data that comes from three arrays. The tooltip code then looks like this:




tooltip.setFormatter("function() { var qualityValuesArray = JSON.parse(this.point.qualities); " +
        "var storedValuesArray = JSON.parse(this.point.stored); " + 
        "var interestingValuesArray = JSON.parse(this.point.interesting); " + 
        "return ''+ this.x +'"+ " '+this.series.name +': '+ this.y +' '+
        'Total: '+ this.point.stackTotal + ' this.point.custom = ' + this.point.custom + " +
        "'
Quality 1: ' + qualityValuesArray[0]  + " + "'Stored: ' + storedValuesArray[0]  + " + "'Interesting: ' + interestingValuesArray[0]  + " +
        "'
Quality 2: ' + qualityValuesArray[1]  + " + "'Stored: ' + storedValuesArray[1]  + " + "'Interesting: ' + interestingValuesArray[1]  + " +
        "'
Quality 3: ' + qualityValuesArray[2]  + " + "'Stored: ' + storedValuesArray[2]  + " + "'Interesting: ' + interestingValuesArray[2]  + " +
        "'
Quality 4: ' + qualityValuesArray[3]  + " + "'Stored: ' + storedValuesArray[3]  + " + "'Interesting: ' + interestingValuesArray[3]  + " +
        "'
Quality 5: ' + qualityValuesArray[4]  + " + "'Stored: ' + storedValuesArray[4]  + " + "'Interesting: ' + interestingValuesArray[4]  + " +
        "'
Quality 6: ' + qualityValuesArray[5]  + " + "'Stored: ' + storedValuesArray[5]  + " + "'Interesting: ' + interestingValuesArray[5]  + " +
        "'
Quality 7: ' + qualityValuesArray[6]  + " + "'Stored: ' + storedValuesArray[6]  + " + "'Interesting: ' + interestingValuesArray[6]  + " +
        "'
Quality 8: ' + qualityValuesArray[7]  + " + "'Stored: ' + storedValuesArray[7]  + " + "'Interesting: ' + interestingValuesArray[7]  + " +
        "'.';}");



The span can also be replaced by a HTML b tag of course.
Too bad you can't use the setHTML() for better formatting, like putting in a HTML table tag. This is a known issue, see here.

Essential in constructing the data is to use the BeanItemContainer, ContainerDataSeries and the addAttributeToPropertyIdMapping() in which you can put custom class (bean) OrderChartItem:



 /**
  * Class with custom value and 3 arrays to be displayed in tooltip
  */
    public class OrderChartItem {

     private static final int NR_QUALITIES = 8;
     
     private final String name;
  private final int x;
  private final Number y;
  private final int someValue;
  
  private final Number[] quality = new Number[NR_QUALITIES];
  private final Number[] stored = new Number[NR_QUALITIES];
  private final Number[] interesting = new Number[NR_QUALITIES];

  public OrderChartItem(String name, int x, Number y, int someValue) {
   this.name = name;
   this.x = x;
   this.y = y;
   this.someValue = someValue;
   
   for (int i = 0; i < NR_QUALITIES; i++) {
    quality[i] = i + 2;
    stored[i] = i + 3;
    interesting[i] = i + 4;
   }
  }

  public String getName() {
   return name;
  }

  public int getX() {
   return x;
  }

  public Number getY() {
   return y;
  }

  public int getSomeValue() {
   return someValue;
  }

  public List getQualities() {
   return java.util.Arrays.asList(quality);
  }
  public List getStored() {
   return java.util.Arrays.asList(stored);
  }
  public List getInteresting() {
   return java.util.Arrays.asList(interesting);
  }
    }


I made the arrays the same size because of its table data, but that doesn't have to be of course.

The basic mechanism is:
  1. Set up a BeanItemContainer with values of OrderChartItem. Take special care when putting in 0 values. The graph then shows the number 0. To avoid that, put in 'null' as a value.

    
    
    BeanItemContainer containerGood = new BeanItemContainer(OrderChartItem.class);
    containerGood.addBean(new OrderChartItem("some name good", 1, null, 701));
    
    


     So the OrderChartItem has the properties that can be reached from the tooltip Javascript. Note the OrderChartItem just gets some name, later it is overwritten by setting the ContainerDataSeries names:

    
    
    seriesGood.setName("good");
    
    

  2. Then wrap each BeanItemContainer in a ContainerDataSeries:

    
    
    ContainerDataSeries seriesGood = new ContainerDataSeries(containerGood);
    
    

  3. Then add the custom properties for each container. First the one value:

    
    
    seriesGood.addAttributeToPropertyIdMapping("custom", "someValue");
    
    

    Then the arrays:
    
    
    seriesGood.addAttributeToPropertyIdMapping("qualities", "qualities");
    
    

  4. And add the series to the configuration:

    
    
    conf2.addSeries(seriesGood);
    
    


In summary: the OrderChartItem is used as data-provider by adding them to the ContainerDataSeries. The properties-mappings of the custom OrderChartItem are then added to the series, after which they are accessible in the tooltip Javascript. Note the "trick" of using JSON.parse() in the formatter and the List getQualities() to get the arrays in the correct format.

Full source code can be found here.
The widgetsets directory has been cleared out because of distribution issues.





Wednesday, February 27, 2013

Vaadin add-on Timeline vs Vaadin Charts add-on comparison


Introduction

For a recent project I had to evaluate the Vaadin Charts and Timeline add-ons for their capabilities.

Setup
- Vaadin 7.0.1
- Vaadin Charts 1.0.0
- Vaadin Timeline which is included in Vaadin Charts since Vaadin 7.0

Requirements
- Be able to see data for 90 days, and the same data but then per week (same graph, just other view).
- Be able to click on a bar and perform some action like retrieving other data based on the selected bar.
- Customizable three code coloring stacked bars.
- Display more details when hovering over a bar.


Comparison

Timeline

This type of graph is very flexible and elaborate in the ways it can display data. It has a "selection bar" below the actual graph so you can determine the values being displayed in the graph above it. See below for a screenshot:



You can try it out yourself in an online demo here and here.

Pros

- The graph is scrollable thus no worries about the bars/lines getting too close to each-other.
- You can zoom in and out, also with your mouse.
- Different charts (graph modes) provided by default, changeable in one click: line, bar and scatter.
- A quick zoom per period (1d, 5d, 1w, 5m etc).

Cons

- Can't attach any action to clicking on a bar. What you can do is react on the area selected with the selection bar, so a work-around might be to have the user select exactly one bar to mimic a kind of "clicking" on that bar. And then take the appropriate action.
- You can only show days on the x-axis it seems. You can't show a bar per week for example (thus one bar representing 1 week). A work-around might be to create your own Zoom period for 1 week, which is quite easy to do.
- Currently as of this writing there are at least two bugs regarding hovering: the timeline moves down a few pixels when hovering for the first time, and the stacked bars have a hovering problem.


Untested

- Can you also add an hover-over tooltip to the bars?


Chart
This type of graph does not have the fancy zooming possibilities, but has more possibilities regarding customization and reacting to events. See below screenshot for an example:


You can try it out yourself here.

Pros

- The bars are clickable and that event can be handled, indicating the x and y of the item clicked.
- The graph is more customizable; for example the numbers in the bars can be removed.
- Different values (like week number) on the x-axis is probably possible (still out for investigating).
- All data is always visible in the current view.
- Customizable hover-over tooltip.
- Does not have the two (smaller) timeline bugs.

Cons

- No horizontal scrollbar or similar, thus for example showing 90 days looks quite "cramped", see the above screenshot. When making the browser smaller, day numbers start to overlap. Do-able, but not optimal. A horizontal scrollbar would be great!
- No space for showing a full date at the bottom. As a work-around the actual date could be added to the tooltip when hovering over a bar (in the screenshot it only says 26).
- No zooming.
- No out-of-the-box other graph modes (i.e like in the Timeline those are provided with it always).


Conclusion

For the project we chose the chart because it is a bit more customizable and the ability to click on a bar and do something with that event was most important requirement.


What's crap about my new Dell Precision M4700 laptop (+ the few good things)

Just got my new Dell Precision M4700 laptop in, which is the top model of their range for 15" screens.
My last laptop was also a Dell Precision, the M4400. Having looked around to see if there's another brand I'd rather take, I still came to the conclusion that a Dell seems to be the best fit for heavy development.

So online I ordered it, with changing the default low 4G or 8G RAM to a more reasonable 16G. That's the first place where I noticed they are really overcharging for the additional RAM: 200 euro (about 261 dollars) for having put in 16G of RAM, while if you buy it seperately it's only about 100 euro. But since I don't like messing around with a brand new laptop (mainly because of warranty) I decided to pay the extra.

Here's a list of pros and cons of the laptop after using it for a few weeks:

Pros
- The power plug is straight, so it fits better in a block of power plugs, see the image below:


- It feels sturdy and solid.


Cons
- The power cord is still quite short. Why not just add 1 meter more?
- The plug that goes into the laptop is straight and at the back. Lots of times when you've got the laptop on your lap it will just fall out. Much better solution: an angled plug and plug it in on the side of the laptop.
- The keyboard has a numeric keypad. Why would you need that on a laptop? How often will data entry people use such a high end laptop? And it forces me to sit more to the left of the keyboard to be able to type... And I know I'll never ever use that numeric keypad... See image below:


- The Page Up and Page Down are placed at such a weird position. And it feels like they should be the other way around anyway: Page Down left, Page Up right...


- Right after I started it up for the very first time, I wanted to search in the Windows configuration screen. The whole machine just froze, I couldn't do anything else but hard-shutdown the machine via the power button. Ok this might be a Windows problem but still it shouldn't happen within 5 minutes...


- It's really hard to turn off the built-in webcam. Out of the box it will switch on automatically if a video stream comes in (or something like that). This should be the other way around by default. You can only turn it really off via system devices, not via the annoying pre-installed webcam control app.

- For creating a VMWare image of the same Windows 7 Professional OS as on this new laptop, you have to enter the Windows COA product key. And guess where they put it on the laptop? No not on the side or the bottom..... it is inside the battery compartment!! So when you're right in the middle of installation, you have to shutdown the laptop, take out the battery, write down the long key, put the battery in, start it all up again.... Djeezzz!!

- Multiple times I've had now that I put a VMWare guest in suspend-mode and that after that the light of the harddisk stays on "forever". I can hear it spin too. Even if after that I close the VMWare application, the harddisk keeps on going. When after that I put the laptop to sleep via the power button, it stops spinning. When I then turn it on again, the harddisk directly starts spinning again. In Windows Task Manager I can't find any process that is supposed to be reading or writing. So what is going on? Who is reading and/or writing? Is the disk crappy? After running diagnostics (during bootup) no problems are found. Is VMWare causing it? I can't reproduce it everytime, so can't really make a support call for it. But all 'n all it doesn't give me much confidence in the machine + setup combinatino.... I'm planning on re-installing the whole OS, so no Dell Recovery manager and other Dell software is on it anymore. Hope that makes it more stable...
- When I plug in a headphones cable on the left, my mouse keeps on bumping into the cable.


Well, that about sums up this "rant".... Next time I'll definitely re-consider whether I should purchase a Dell again....







Saturday, February 2, 2013

Lessons learned Spring 3.0 project

Another quick post with some bullet points of lessons learned during my last Spring 3.0 project.
Tools used were:

  • Spring 3.0 (3.1.2 when moving to Hibernate 4)
  • Spring MVC
  • Spring WebFlow 2
  • Spring Batch
  • Spring Agent Framework
  • Hibernate 3.2.6
  • Apache CXF
  • JBehave (Behavior Driven Development)
  • Tiny bit of GXT, which is a Google Web Toolkit extension based on GWT and ExtJS technology. Formally known as Ext GWT, GXT is a Java library for building rich internet applications with the Google Web Toolkit (GWT)
  • JBoss jBPM 5.2.0
  • JBoss Drools 5.3.1
  • Jenkins for CI
  • Crucible for code reviews
  • Jira for issue tracking en sprint user stories and tasks and progress tracking
  • SpringSource Tool Suite 2.9.1 as Eclipse based IDE
  • JUnit 4.8
  • EasyMock 2.5.2, EasyMockSupport
  • Java 6
  • Cargo-itest (introduction here) for integration testing
  • Tomcat 6
  • Oracle SQL Developer (free version)
Besides learning new tools like JBehave an Cargo-itest, below are some things picked up using above tools.

Spring MVC
Had some issues getting REST-like services available, mainly from inexperience, but good to know what's working and what not:

    @GET
    @Path("some-path/?role={roleId}")
    Response getSomeOtherUrlParams(@PathParam(value = "roleId") final String roleId);
    // Unable to call it, because PathParam is not existing (it's not really part of the path)

    @GET
    @Path("some-path2/role/{roleId}")
    Response getSomeOtherUrlParamsNotAsRequest(@PathParam(value = "roleId") final String roleId);
    // Works

    @GET
    @Path("some-path3/")
    Response getSomeOtherUrlParamsAsQueryParam(@QueryParam("role") final String roleId);
    // Works, will have clients of this service (like SoapUI) already generate a ?role= parameter

    @GET
    @Path("some-path4/")
    Response getSomeOtherUrlParamsAsRequestParam(@RequestParam("role") final String roleId);
    // Won't generate a ?role= parameter for clients, the WADL also looks different for this method/parameter

Don't use @FormParam for submitting XML because if the mediatype is text/xml for example, the mapping on the REST method fails because JAXRS expects HTML (default if not specified at the service definition).
So suppose the service is defined as:

    @POST
    @Path("message/{messageCode}/send")
    Response sendMessage(@PathParam(value = "messageCode") final String messageCode, 
                                         @FormParam("moduleName") String moduleName,
                                         @FormParam("userId") String userId, 
                                         final MessageContentsRequest messageContents);

When invoking the above method by POSTing to 

http://localhost:8080/webservice/v1/message/SECRET_MESSAGE_CODE/send 

with as POST body parameters moduleName and userId + the MessageContentsRequest XML, that gives the following error in the log:

2013-01-05 11:24:38,491 WARN  [http-8890-1] JAXRSUtils#processFormParam - An application/x-www-form-urlencoded form request is expected but the request media type is text/xml;charset=UTF-8. Consider removing @FormParam annotations.
2013-01-05 11:24:38,493 WARN  [http-8890-1] WebApplicationExceptionMapper#toResponse - WebApplicationException has been caught : no cause is available

The same error occurs when invoking: ..../send/?moduleName=abc&userId=ttl  (so adding them as POST URL name/value parameters).

So make sure you use in that case:

    @POST
    @Path("message/{messageCode}/send")
    Response sendMessage(@PathParam(value = "messageCode") final String message ode, 
                                         @QueryParam("moduleName") String moduleName,
                                         @QueryParam("userId") String userId, 
                                         final MessageContentsRequest messageContents);

The moduleName and userId can then be passed in via the POST URL name/value parameters.


EasyMock
Use replayAll() and verifyAll() instead of replay(mock) and verify(mock) for each mock seperately. Requires extending EasyMockSupport.
 


SQL Developer
The free SQL Developer can't export clob column values. Nor can it import clob values larger than 4K.




 

Saturday, January 12, 2013

Got my Kickstarter BERO remote controlled robot in

Last year I funded one of the BERO robots via Kickstarter. And last week the matte finish black Basshead arrived! This is wat it finally looks like:

Quite a sturdy little robot, remote controllable via an Android and iOS app. Biggest challenge was to switch it on! In the supplied instructions it said to turn it on and off, but I just couldn't find the on/off switch! Almost sent out a question for this to the support, but then I found it! Below a picture, should you come across the same problem.
And some Android app screenshots with which you can control the Bero: Splash screen
Home screen
Control screen to let Bero react (dance) to sound/music
Eight programmable modules for its movement/lights/sound
So, quite funky!

Wednesday, October 24, 2012

How to remote debug PL/SQL with Oracle SQL Developer

There are quite a few posts (see the References section at the bottom) on how to debug PL/SQL remotely. That is when you want to step through another session that is running some PL/SQL procedure. Still I didn't find it obvious to get it working. Mainly because none of the blog posts have a diagram on how all components are connected and which component tries to connect with the other component(s). Therefore this post, including..... a diagram! :)

Setup
- on a Windows 7 desktop PC running SQL developer with IP xxx.yyy.19.84
- remote database running in VM with IP xxx.yyy.19.104. This one actually is running on the above desktop machine, but that should not change much if it would be on a separate machine, except there might be some firewall(s) in between.

In short this is what we are trying to achieve:


  1. Start up the remote debug listener on port 4000 on the desktop machine
  2. Start up SQl*Plus connecting to the remote database
  3. Connect to the remote debug listener from the database. This might be the tricky part for some: SQL*Plus is connected to the remote database, so the DBMS_DEBUG_JDWP.CONNECT_TCP call is executed from that database! So that machine (.104) needs to be able to get to the machine *.84) with the remote debug listener listening on port 4000
  4. Execute the stored procedure from the package you want to debug and SQL Developer will stop at the set breakpoints

Detailed steps
  1. As mentioned in all the referenced blog posts, right-click on the connection and select Remote Debug....
  2. In that popup enter:

    If done correctly, you should see something like:

    If you get a message saying "The debugger is not able to listen for JPDA using the specified parameters." then there is something wrong with the parameters you entered. Yes that's all I could figure out too... Potentially you entered the wrong Local address or maybe there's already something listening on the specified port.
    Note that the Local address is the IP of the machine SQL Developer is running on. Unless your database is running on that same machine, you can't use 127.0.0.1 (localhost). You have to use the full IP address.
  3. Since the database (which is on xxx.yyy.19.104) needs to connect to the SQL Developer remote debugger listener, it must be able to get to that machine (xxx.yyy.19.84). You can test this by trying to telnet to it and the port the listener is listening on from the .104 machine:
    
    
    $ telnet xxx.yyy.19.84 4000
    Trying xxx.yyy.19.84...
    Connected to MyPC (xxx.yyy.19.84).
    Escape character is '^]'.
    JDWP-Handshake
    ^]quit
    
    

    Look for the JDWP-Handshake string. If you get that, you know it can get to the remote debug listener on the .84 machine. Also note that when you quit the telnet session, the listener is stopped, so make sure to start it again!
  4. Start up an SQL*Plus session from the .84 machine to the remote database. For example:
    
    
    sqlplus user/passwd@xxx.yyy.19.104/oracle
    
    

  5. Connect to the remote lister on the .84 machine:
    
    
    SQL> set serveroutput on;
    SQL> execute DBMS_DEBUG_JDWP.CONNECT_TCP('xxx.yyy.19.84',4000);
    
    

    Note it is the .84 machine and of course port 4000 where the remote debugger is listening on.
  6. In SQL Developer, set a breakpoint in a procedure you want to debug, e.g test_me. Tip: as a first try, set it at the first line of code in the method test_me, then you know for sure that if it gets called, it should stop there definitely.
  7. Now call that procedure from the SQL*Plus commandline, e.g:
    
    
    DECLARE
      P_ANR VARCHAR2(25);
      P_RESULT VARCHAR2(4000);
    BEGIN
      P_ANR := 666;
    
      SOME_PCK.test_me(
        P_ANR => P_ANR,
        P_RESULT => P_RESULT
      );
      DBMS_OUTPUT.PUT_LINE('P_RESULT = ' || P_RESULT);
    END;
    /
    
    

    Note the test_me method has an IN and an OUT parameter.
  8. Your SQL Developer should now stop in the breakpoint you've set, see below SQL Developer screenshot of the Remote Debug session log window:


Remarks
I did not seem to need to run:


GRANT DEBUG CONNECT SESSION TO some_user;
GRANT DEBUG ANY PROCEDURE TO some_user;
/



You might still need to execute these commands if you get the permission errors you see in Step 5 here.

If you have a firewall on the .84 machine or between the two machines, you might need it to allow port 4000 to be accessed from the (other) DB machine...

References
Using SQL Developer to debug your anonymous PL/SQL blocks
Remote debugging with SQL Developer revisited
Application Express Community - Remote debugging
Sue's Blog... again... - Remote debugging with SQL Developer
Barry McGillin - Remote debugging with SQL Developer
Debug ApEx App with SQL Developer
Use Oracle SQL Developer to aid Oracle Application Express development
OTN Discussion Forums - Remote Debugging

Wednesday, October 17, 2012

Lessons learned Seam 2.2 project

Quick post with some bullet points of lessons learned during my last (and first) Seam project:

  • Seam 2.2
  • Hibernate 3.3.1
  • JEE 5
  • EJB 3.0
  • JSF 1.2
  • Richfaces 3.3
  • JBoss 5.1
  • Drools 5
  • SQLServer 2008
  • MySql 5.x
  • TestNG
  • Hudson
Seam
- To have a navigation for a method with a parameter like exportSelected(String value): see Seam navigation based on a function with parameters
- If your Seam app keeps re-deploying (restarting) when using JBoss within Eclipse: delete all files that end with 'dia' in your WEB-INF dir. See: Seam keeps redeploying For example, I modified pages.xml, which apparently created a pagesdia (or similar) file... after removing that one, it worked fine again.

Hibernate
- To see which validator fails on a persist():


    try {
        entityManager.persist(verbinding);
    } catch (InvalidStateException e) {     
        for (InvalidValue invalidValue : e.getInvalidValues()) {         
            System.err.println("--------------> create(): exception Instance " +
               "of bean class: " + 
               invalidValue.getBeanClass().getSimpleName() +                  
               " has an invalid property: " + invalidValue.getPropertyName() +
               " with message: " + invalidValue.getMessage());     
        } 
	throw new RuntimeException(e);
    }




MySql
- To see data in better format in MySQL: show engine innodb status\G (so add the \G to a query)

Hibernate/JPA
- NamedQueries are loaded and parsed at startup time, so that's an advantage above em.createQuery() which are only parsed and evaluated at runtime. So with NamedQueries you get the errors sooner.

Drools
To solve this error in Drools decision table .XLS spreadsheet:


   [testng] DEBUG [test.service.drools.DroolsHandling] getKnowledgeBase: getting path info from settings service
   [testng] DEBUG [test.service.drools.DroolsHandling] getting test.service.drools.DroolsHandling ruletable file: HandlingModelTest.xls
   [testng] DEBUG [test.service.drools.DroolsHandling] getKnowledgeBase: Trying to open a File
   [testng] WARN  [jxl.read.biff.NameRecord] Cannot read name ranges for Excel_BuiltIn__FilterDatabase_1 - setting to empty
   [testng] WARN  [test.service.drools.DroolsHandling] There are errors in the decision table file
   [testng] WARN  [test.service.drools.DroolsHandling] Decision table error: message=[ERR 102] Line 9:16 mismatched input '"DroolsHandling6"' expecting ']' in rule "DroolsData_12" in pattern stringDataset
   [testng] WARN  [test.service.drools.DroolsHandling]     line: 9
   [testng] WARN  [test.service.drools.DroolsHandling] Decision table error: message=[ERR 102] Line 9:35 mismatched input '"Prefab"' expecting ']' in rule "DroolsData_12" in pattern stringDataset
   [testng] WARN  [test.service.drools.DroolsHandling]     line: 9
   [testng] WARN  [test.service.drools.DroolsHandling] Decision table error: message=[ERR 102] Line 22:16 mismatched input '"DroolsHandling6"' expecting ']' in rule "DroolsData_12" in rule "DroolsData_13" in pattern stringDataset
   [testng] WARN  [test.service.drools.DroolsHandling]     line: 22



Make sure your definition has all the cells merged that contain a condition! For example, check the red arrow pointing at the border of the cells between G9 and H9.
The last cell for the condition Prefab is not merged (i.e one cell) with all the other conditions. Here's now how it should be: see again the red arrow, there's no cell separation anymore, all condition columns are now merged into 1 cell.


- A space in a condition in a decision spreadsheet can cause a non-match! E.g GATE VALVE is not matched. Fixed it by always replacing a ' ' with an '_'.

Sunday, May 20, 2012

BlackBerry Playbook Tablet for Android porting

Houston, we got it in the mail: a free RIM Blackberry Playbook Tablet because I submitted on time an Android app to the BlackBerry App World. The Android app runs within the Playbook Android emulator (yes so the app is native Android! Pretty cool I'd say!

Booting
Below a few bootup screenshots:

The bootup time for the device is quite long, definitely longer than the Samsung Galaxy Tab 10.1 for example.

Home-screen and taskmanager
This is what the home-screen looks like:
This is what the taskmanager looks like:
The way to go back to the home-screen or switch to another app is by swiping from the button of the screen up.

The simpel app running
And the app runs fine on the real device too :) Those Android figures are also checking it out as you can see...
Those four buddies are called: Don Pablo Calaveroid, Longevity, Fortune and Blessing. The last three were released because of the chinese Year of the Dragon. Too bad they sent one with still the USA power plug attached to it. Luckily I had a converter (as the globetrotter I am ;)...
This is how the tablet 7" screen compares to the Samsung 10.1 (note both screens are very clear, the picture is only trying to show the difference in size of 10" vs 7"):
Tetris just runs fine on it (but that's a native app), the screen is very clear:


Some generic feedback:
  • The tablet smells rubbery because it has rubber on the back. Probably done as anti-slip, but even your fingers start to smell like it.
  • A bit annoying was that you have to watch the Basics tutorials when doing the setup.
  • Like the virtual keyboard, feels like lot less typos than similar sized touch screens


Porting
During converting the app to the PlayBook Android 2.3.3 or higher runtime, I ran into a few issues:
  • For emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, emailBody) statements needed to add .toString() to emailBody because otherwise you get an exception telling that EXTRA_TEXT is not a String.
  • After each re-install of the app on the emulator, nothing seems to be remembered of the preferences. E.g had to accept the EULA each time after a new deploy, while this does not happen on the "regular" Android emulator. Also works fine on the real Playbook device.
  • In my app the user can swipe left to right and vice versa to browse through tips. On the Playbook emulator the text on bottom of the screen was not always correctly updated. It was lagging behind.
  • When trying to open the browser with an Intent and URL in the simulator, it says 'No network connection'. Strange, the pre-installed browser on the home screen works just fine... Seems similar to this reported issue.

Thursday, April 12, 2012

JBoss exception Transaction is not active: javax.transaction.RollbackException: The transaction is not active!

Sometimes you get this exception when using JBoss (5.1.0 and Seam 2.2 in my case):



14:08:17,312 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TransactionReaper_18] - TransactionReaper::check timeout for TX -3f57fd63:6ae:4f5df31e:b7in state RUN
14:08:17,312 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.BasicAction_58] - Abort of action id -3f57fd63:6ae:4f5df31e:b7 invoked while multiple threads active within it.
14:08:17,312 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.CheckedAction_2] - CheckedAction::check - atomic action -3f57fd63:6ae:4f5df31e:b7 aborting with 1 threads active!
14:08:17,343 WARN [JDBCExceptionReporter] SQL Error: 0, SQLState: null
14:08:17,343 ERROR [JDBCExceptionReporter] Transaction is not active: tx=TransactionImple < ac, BasicAction: -3f57fd63:6ae:4f5df31e:b7
status: ActionStatus.ABORTING >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: -3f57fd63:6ae:4f5df31e:b7 status: ActionStatus.ABORTING >)
14:08:17,359 ERROR [TxPolicy] javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Cannot open connection
14:08:17,375 ERROR [TxPolicy] javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Cannot open connection
14:08:17,453 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TransactionReaper_7] - TransactionReaper::doCancellations worker Thread[Thread-10,5,jboss] successfully canceled TX -3f57fd63:6ae:4f5df31e:b7
14:08:17,500 WARN [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.BasicAction_40] - Abort called on already aborted atomic action -3f57fd63:6ae:4f5df31e:b7
14:08:17,515 ERROR [AsynchronousExceptionHandler] Exception thrown whilst executing asynchronous call javax.ejb.EJBTransactionRolledbackException: Transaction rolled back at org.jboss.ejb3.tx.Ejb3TxPolicy.handleEndTransactionException(Ejb3TxPolicy.java:54)
at org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:175)
at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:87)
at org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:190)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.tx.NullInterceptor.invoke(NullInterceptor.java:42)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.security.RoleBasedAuthorizationInterceptorv2.invoke(RoleBasedAuthorizationInterceptorv2.java:201)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.security.Ejb3AuthenticationInterceptorv2.invoke(Ejb3AuthenticationInterceptorv2.java:186)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:41)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.BlockContainerShutdownInterceptor.invoke(BlockContainerShutdownInterceptor.java:67)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.aspects.currentinvocation.CurrentInvocationInterceptor.invoke(CurrentInvocationInterceptor.java:67)
at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
at org.jboss.ejb3.session.SessionSpecContainer.invoke(SessionSpecContainer.java:176)
at org.jboss.ejb3.session.SessionSpecContainer.invoke(SessionSpecContainer.java:216)
at
org.jboss.ejb3.proxy.impl.handler.session.SessionProxyInvocationHandlerBase.invoke(SessionProxyInvocationHandlerBase.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:32)
at org.jboss.seam.intercept.ClientSideInterceptor$1.proceed(ClientSideInterceptor.java:76)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
at org.jboss.seam.async.AsynchronousInterceptor.aroundInvoke(AsynchronousInterceptor.java:52)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
at org.jboss.seam.util.Reflections.invokeAndWrap(Reflections.java:144)
at org.jboss.seam.async.AsynchronousInvocation$1.process(AsynchronousInvocation.java:62)
at org.jboss.seam.async.Asynchronous$ContextualAsynchronousRequest.run(Asynchronous.java:80)
at org.jboss.seam.async.AsynchronousInvocation.execute(AsynchronousInvocation.java:44)
at org.jboss.seam.async.QuartzDispatcher$QuartzJob.execute(QuartzDispatcher.java:243)
at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:529)
Caused by: javax.transaction.RollbackException: [com.arjuna.ats.internal.jta.transaction.arjunacore.inactive]
[com.arjuna.ats.internal.jta.transaction.arjunacore.inactive] The transaction is not active! at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1414)
at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:137)
at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
at org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:170)
... 47 more



For 99% sure this is caused by a timeout. Unfortunately you can't immediately see that from the exception.

The fix
Increase the timeout in transaction-jboss-beans.xml in your JBoss deploy dir. For example if you have JBoss installed in:

C:\jboss-5.1.0.GA\server\default\deploy


then open transaction-jboss-beans.xml and set the field transactionTimeout much higher. E.g at 3000 (default is 300).
In older JBoss version this setting used to be in default/conf/jboss-server.xml, see here.
The official documentation can be found here.

Saturday, April 7, 2012

Why cancel-button now also first on Android?

Just wondering about this big time...

Why is Android switching the standard buttons order for Android 4.0 and higher? See for example this screenshot, the cancel button is now the first button!
I was always taught that what the user most likely wants to do should be first, and so the dismissive button last.
And Cancel is usually not what to user wants to do!

Apple is doing it also in their iOS, is Android now just plainly copying it?
Definitely prefer Cancel at the end, (western) reading occurs from left to right, so now one always has to read/skip the dismissive button!

From the expert the conclusion is just to be consistent. That means at least within the Android platform it is not consistent between pre 4.0 and 4.0+... I think an unneeded and unnecessary change.

Thursday, February 16, 2012

Summary of presentation with tips on creating visually appealing Android applications that scale to various screen sizes

Interesting points from a presentation in which Eric Burke shares tips on creating visually appealing Android applications that scale to various screen sizes.
The session focuses on custom views, rounded corners, scalable drawables, gradients, shine, holograms etc.
Eric is the St. Louis Engineering Manager and Android Tech Lead with Square.

06:00 Use alpha compositing with anti-aliasing, not canvas.clipPath() because you get jagged pixelated corners

17:25 triangular shine

29:00 Check the imeOptions for EditTexts/keyboard input. E.g imeOptions="actionNext" for example. Or "actionDone". NOTE: does not work on HTC Sense UI! So "actionNext" seems safest (might work, might not, but no harm done). For "actionDone" you still need a button for it on your screen anyway.
31:00 SafeViewFlipper code because the standard one crashes your app eventually (guaranteed) on pre-HoneyComb!
35:00 Holograms! Using the accelerator it acts as if it's shiny. Cool!
39:00 demo of cards sliding in/out and hologram on emulator

Wednesday, February 1, 2012

Android Tablet project lessons learned

A little while ago I had the opportunity to work on a tablet-only app, running Android 3.1 or higher.
The app consists of only one screen, on which everything can be done.Therefore the app basically consists of one Activity and several Fragments (about 4 to be precise).
The Activity controls the Fragments for potential re-use of the Fragments, as recommended. As a basis the official Honeycomb Gallery project was used. Quite unique to this app was though that it has to have 3 columns instead of the usual 2...

To get a feeling for the app, here are some screenshots:


The very first time the user has to log in, after which a sync with the server is done. Note the progress bars showing in the background.


Synchronisation in progress... Notice that the title is indented for some reason. Needs some investigation why it shows here, and not in the Login DialogFragment...


After the data has been loaded, the user can filter all data by selecting from the dropdowns, and selecting items in the lists. The rightmost column then shows a list of questions, dependent on the selected filters. Many types of questions and actions had to be supported; beside the ones you see, also built were dropdowns, multiple radiobuttons, view images and PDFs, etc.

Lessons learned

  • Prevent EditText in a ListView from losing focus/text disappearing:

    android:windowSoftInputMode="adjustPan"



  • Sometimes Eclipse/DDMS loses the connection with the emulator, it just can't "see it" anymore. For example when you want to run an app in the emulator, the start dialog does not show your running emulator. To fix that run 'adb kill-server', that restarts the server. After this the emulator should be detected again.

  • You can't disable a RadioGroup such that it disables it contained radiobuttons. You have to disable each radiobutton within that radiogroup seperately.

  • Note that all Activities in your app might have had onDestroy() called, but the Application instance might still be around! So watch this when you have static variables, like a reference to the database... it might still be open when your Activity's onCreate() is called again!

  • The 4.0 emulator was taking ages to start up (gave up after about 30mins). Then I compared the settings of my 3.1 AVD with the new 4.0 AVD. It turns out that the hardware property 'Abstract LCD density' setting of the 4.0 AVD is set by default to 240, which means high density. That implies a lot of pixels to draw. Read somewhere that that's one of the issues of a slow starting AVD. So I changed that property to be the same as of the 3.1 AVD, so made it 160. After that, the 4.0 AVD started about as fast as the 3.1 AVD (several minutes).
    I also reduced the 'Device ram size' setting from 512 to 256, but don't think that was the one that fixed it. See also here.

  • The monkeyrunner recorder script records really slow on a 160 density Android 3.1 AVD. Playback is a lot faster. Here's a bunch of instructions, including 2 scripts for recording/playback which you can also find here and here.

  • Had a problem trying to show the progress indicator in a ListFragment with the correct top margin. Tried adding addHeaderView() on the fragment to show some text above a fragment list. That works fine, except: when showing the progress indicator, it does not take into account any (top margin) LayoutParams being set! The app needs to set that to stay below the Action Bar which is in "overlay" mode. As soon as the adapter is set, the margin is taken into account correctly. I found the following here: in onCreateView() there is also a similar problem, they set the layoutparams explicitly again:


    // For some reason, if we omit this, NoSaveStateFrameLayout thinks we are
    // FILL_PARENT / WRAP_CONTENT, making the progress bar stick to the top of the activity.
    ... code where LayoutParams are set ...

    Tried several combinations of setting the LayoutParams, but those didn't work either. As a workaround now I have added a FrameLayout above the <fragment> in the main Activity xml. It is a <fragment> with a FrameLayout with a LinearLayout with some TextViews in it. For that one the top margin is set, and since the ListFragment is below it, it is pushed down correctly, even when the progress indicator is shown. Maybe the FrameLayout is causing the problems? Maybe a custom list fragment element would fix it.

  • How to open a PDF from an app's local private storage:


    // Assuming you have stored a file like this (note the mode):
    FileOutputStream f = context.openFileOutput("theFilenameYouStoredThePdfAs", Context.MODE_WORLD_READABLE);

    ... write bytes to f, do other stuff...

    // Then you can get a reference for it for an external app via:
    File file = activity.getFileStreamPath("theFilenameYouStoredThePdfAs");
    if (file.exists()) {
    Toast.makeText(activity, activity.getString(R.string.openingFile), Toast.LENGTH_SHORT).show();
    Uri path = Uri.fromFile(file);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(path, "application/pdf");
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    activity.startActivity(intent);
    }


  • Definitely take a look at the latest Google I/O app, that's always a good reference for figuring out how to do specific things; I consider it a best-practices app (I've also seen it being referred to in blog posts/presentations from the Android team, can't find them now :).

Android invasion of the Mini Collectible - Series 02!


They are 16 in total, but sort of left out the doubles: Bernard, Hexcode, Cupcake, GD-927, Iceberg, Greeneon, ???, Blackbeard, Racer, Bluebot, Hexcode, Rupture, Noogler.