Thursday, December 30, 2010

Synchronized Android and iPhone development

I have been working on both the Android and iPhone. We plan on releasing a new version of our app on the iPhone and the first version ever on the Android. My goal was to get the Android side to match the feature set of the iPhone side. Of course I ended up making some improvements on the Android side while I was at it. That meant I had to move those changes over to the iPhone.

At first I was pretty worried it was going to take a long time to move the changes over. Turns out I know just enough on the Xcode side that I was able to move almost all the changes over in less than one day. I am still not a big fan of Xcode / Objective C but I am getting faster at using it.

Since I had them both running it was time for some side by side comparisons. First thing I found out was our appointment recurrence code was not correct on the iPhone. The original author is no longer with the company so I needed to just figure it out. I had written that code from scratch on the Android earlier in the week. Lots of bitmask operations and all sorts of fun date manipulations. I thought porting it would be a big pain but it was not bad. I just looked up "day of week NSDate" and a few other things to be able to get to the source code snippets I needed. The one big difference is pulling time in Java is in milliseconds and in seconds in Objective C so I changed my math by a factor of 1,000 to fix that. Everything else ported very cleanly.

Below are my thoughts on differences I found. I am not saying either one is better than the other, just listing differences and annoyances I found with each platform. I have not used either platform for an extensive period of time.

Differences:
(iPhone is really a version 2 iTouch, Android is my Samsung Galaxy S)

iPhone - Top of screen is used to show where you are and have a button to get back to previous screen. Gives you a bit of an odd look as the "Title" of the screen is not centered due to the button to its left.

Android - Title is over data and centered. There is a back button on every phone to take you back to the previous screen. Of course you don't always know where back happens to be.

iPhone - Layouts we are using take longer to generate and paint. Most likely due to slower hardware and I don't believe the layout code being used is very optimized. It is using WebUI for some things and I am sure that part of the issue.

iPhone - rotating phone forces you to watch the rotate animation. Look, animations are cool and all but I really just want to get work done. Maybe they should allow you to turn that off.

Both - the standard date picker stinks. They are both very similar with iPhone doing a fancier pinball wheel display but they both suck when you want to get to a specific date quickly. On the Android side phones are not consistent in what they show, some show the day of week which I think is very useful and others don't.  Pressing a [+] button 10 times to get to a date in the past is crappy. Trying to stop a spinning wheel with any sort of accuracy is no fun either. At least on the Android you can click on a field (month, day, year) and just type what you want. On the iPhone all you get is the spinner.

Both - Each platform has a perfectly fine date chooser in their base calendar applications. Oddly only the one on the Android allows you to rotate the screen. What logic Apple used to decide which base apps supported screen rotation and which ones don't truly escapes me. I need to find or write a date selection control similar to the base calendar controls that shows an entire month at a time with simple arrow keys to change month or year. Obviously people can accurately finger pick a date this way.

Android - I wish you could just plug-in any phone and dump the APK to it. Instead I end up hunting down USB drivers to make things work. Some phones have the USB drivers on them and will transfer them to the computer when you plug them in and others require a web search. We need to set up a mini-market on a server so I can just put the app there to let QA get to it. Once the driver is installed the copy to device is very easy, just the initial setup that stinks.

iPhone - You must provision each device with Apple to install anything on it. All devices use same USB driver. Every time you plug in a device is fires up iTunes and wants to sync which I find annoying. I run / debug in the simulator and only plug in the device for final testing. I have only ever used one iPhone device on my Mac so I don't know what will happen when I attempt to plug-in another one.

iPhone - I hate typing my password, which has numbers and uppercase letters. Too many presses to navigate to numbers etc. Android is easier with the long press to get to the one number I have in my password. Of course I have to type this a ton of times a day during testing. This is where the Simulator comes in handy, just type on the keyboard.

Android - Fonts look better. Bold looks more bold. I am using bold to help the data stand out from the labels. It just looks better on my Android device than on my iTouch device. I know it is a much better screen. I really want to see what it looks like on an iPhone 4.

iPhone - Touch input seems a bit more accurate. We have images next to certain lines of text that you press to invoke a mini-text editor. Seems to work easier on the iPhone. Could be a bigger touch target, could be screen differences.

iPhone - If you have a table with sections headers and you scroll the table the section header gets stuck at top of screen until the next section header hits it, the first section header then scrolls off and the second section header sticks. This is actually a pretty cool effect that you get for free.

iPhone - I still don't like Interface Builder and Xcode being separate applications. I did set up Spaces on the Mac and I plan on running each on their own virtual screen. I wish I had a dual monitor setup on my Mac like I do on my PC. I find it too easy to click off the UI element I am working on to get the focus rectangle to disappear so I can see what the layout looks like and accidentally click on some part of Xcode meaning the whole focus shifts and I have to meta+tab back to Xcode. This should be fixed with Xcode 4 when that gets released.

Totally random side note that still applies to this as it affects my programming

I asked for and got a set of Sennheiser wireless headphones for Christmas. The wired headphones I used at work had a pretty short wire and if moved to my right to use the Mac I would get a tug. Wireless seemed the way to go. I checked with some buddies and most said wireless sucks in general unless you get Sennheiser. The RS-120 seems like the way to go, decent price and rechargeable.

The verdict? I am very happy with the headphones. They were shutting off on me at first then I found a web post that you need to crank up the volume out of the computer and use the volume adjustment on the headphones to tweak the sound. Once I did that no more issues in that area.

Sound quality is very nice. They do a good job covering my ears. I can still hear people around me if needed. Could be a little more bass but that could be my sound source too. I did some tweaking in my sound card settings panel to help them out.

They are lighter than I figured they would be. I have Sony wired headphones that weigh more. Nice to not have much weight on your head. Good padding on the top of the adjustable over the head band.

The one thing I don't care for involves pausing sound. If the headphones are not getting a sound signal they output static, not super loud in my case as I don't run them loud at work but if your sound feed stops you get an earful of static. I generally hit the off switch on the headphones and pause button at same time. I also start the music before I put the headphones back on. User training but I wish it just went silent instead of static.

Tuesday, December 21, 2010

Finally have my app on the iPhone building / installing

This has been a long drawn out battle with the Mac, Apple and Xcode but I finally have my app running on the iTouch. It all starting trying to get an account with Apple so I could get a developer key. The account got hosed and it took many phone calls and emails to get it fixed. It was over a month and a total refund with cancellation of account to make it happen.

Yesterday I finally get a developer certificate set up. I plug in the iTouch which fires up iTunes which wants to do an install of latest iOS. I let it do that and then fire up Xcode where it says it can not work with the attached device even though it is registered with Apple under my account. I try to provision it but that only annoys it as I am not the company administrator. After looking up various things on the web I land on the dialog that states my SDK it out of date so Xcode can't deal with the device.

Hit the Apple site to download the SDK update, it is 3.5g! Honestly I could download every Java IDE out there for pretty much every OS and not add up to 3.5g of download. There is no "upgrade" either, you download the whole damn thing. Guess that makes it easier for Apple and keeps every Apple user out of trouble but it is a huge waste of bandwidth. Nothing I can do about it so I start the download knowing I will not be able continue my experiment until the next day.

This morning I installed the new SDK. That goes cleanly so I plug in the iTouch. It does not recognize it but asks to talk to it for a bit to discover things. No problem there. Do a build but it will not work on the device as the architecture does not match. I dig around in project properties - and remember this has all worked just fine in the iPhone simulator - and change it all from armv7 to armv6 and armv7. Build and it is mad, clean and build and it is still mad. Do more Google work and find another place you have to set it to armv6. Build and mad, clean and build and it seems to be mad but still it pops up on the device.

Runs just fine but I see a small change I want to make so I adjust the code and the build / run on device is no longer mad. Everything appears to be in working order at this time but it sure was a long battle. I know I had a minor battle with my Android phone to get proper USB drivers installed but everything else was very smooth. Even the USB battle only took a few hours tops, the iTouch battle was over a month. I really wanted this in place before Apple shut down for the holidays. Happy it is working but very frustrated with the overall developer experience with Apple.

Today I get to go back and work on the Android code base. Will be much happier over there but I am really glad I got the code pushed to the iTouch finally.

Thursday, December 9, 2010

I published my first Android game tonight

Must say this is a big ball of stress that I can throw back into the void. I began working on the game in October while I had some time off between my old and new job. Always good to take a little time off after giving notice.

If you have an Android phone and like logic puzzles do a search on "Grid Hunt" on the Android Market and give it a shot. The game is free. I decided to go the AdMob route to see how that works. Seems like a good time of the year to release a game. Lots of people will be getting new phones and people will be taking time off so they will have some extra gaming time. I know I hit the market every so often to see if there is something new to play for a few minutes.

Actually writing the game was the easy part. It became a real headache when I tried to tie in AdMob. There is not much in the way of documentation which is very unlike the rest of my Android experience. First off I wrote a game so I was using the full screen. I had forgotten that I plugged my view directly into the activity instead of doing it via the XML file. I put all the AdMob stuff into the XML and of course nothing happened.

I changed the code to use the XML to drive the view which meant a shuffle of some other things as I was not using the XML based constructor. I got that working but no AdMob so I shoved in a simple label and it painted on top of my game screen. I realized my game code was a custom view so I needed to tell the layout manager what my size happened to be. I added the following code:


/**
* We want to take up almost all of the screen
*/
@Override
protected void onMeasure(int width, int height) 
{
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int dispWidth = display.getWidth();
int dispHeight = display.getHeight();

setMeasuredDimension(dispWidth, dispHeight - 48);
}


This put the label at the bottom of the screen. I was then able to piddle around with the AdMob code to get it to appear. I really don't remember everything I did to get that to happen. I needed to get some options configured and I finally got some output in the less of reliable Android log window of Eclipse. I was able to get the special code line from the log console to show the test ads.

I configured a private key and used the really handy Eclipse export signed APK menu item and created a nice ready to roll APK.

I wanted to get this rolling tonight but I wanted to clean up the help text and add some separators in the options dialog. I plugged in my phone and it would not install the app! What the heck is going on here? I have been using my phone at work to develop our app there and I have been using IntelliJ. I did find a quick answer on the web. I just need to adjust the time out value in Eclipse. Then it started working again but not until my wife closed the door to the office due to my choice words of "I don't need to to ^%%$# happen to me tonight!"

When I tried to pull up the options screen it crashed. How could this happen? Turns out when I switched to the other XML style view loading I used same variable name hiding the one in the main class. I thought I had that warning enabled in Eclipse but I did not as I had started a new Eclipse workspace when I renamed parts of the project which annoyed SVN. I hate that part of Eclipse, I want my workspaces to all have the same warnings settings. I enabled nearly all warnings, fixed a few of those in the code and did the easy fix for the view issue. Once I figured it out so I was back on my way to having a fully functional app.

The Android Market Website was really easy to use. I plugged in my information and uploaded the file along with screen shots and descriptive text. I knew I had an issue with new ads not appearing but I though that would only happen after it was on the market. I was wrong. Ads still were not working, you got the first one but it never changed. So I checked the AdMob site and set it to override the interval then the ad did not show up at all! I unpublished the app and started doing Google searches.

I found out I have to set the interval which I tried to do in the attrs.xml file but I had no luck there. I never got the other attributes to work via the XML file but I did not really care to override the base colors. Now I needed something to work. I piddled around with that for too long and then found I could just do it directly in code so I set it up for 30 seconds. It worked as I tested the phone via the debug cable so I put it out on the web again after upping the version numbers.

Of course I sent out a note to all my buddies that have Android phones and put an update on my LinkedIn profile. Finally the brain dump on the blog. I sure learned a lot and I still think this is easier than the iPhone hell I have been going through with just getting a developer certificate. I still can't put my iPhone code on a stinking device.

I know I have been using a lot of things I learned from my game development for my work development. I was able to really chug along today at work on the login screen that I am redoing. Now I can take all my market place set up knowledge and use that too. We will not use Ad Mob for the app I am doing at work but if this game makes me a few bucks I am sure I will write a few more apps outside of work and I plan on using ads again. Since I now have real working code to look the next time it should fall into place really easily.

It will be interesting to see what comments I get on the game, how much money I am able to make off of it and how many downloads occur. I have some ideas for a two player mode which would make a really nice update if the games gets a few eyes on it. Always good to have an update after the game has been out a bit and I don't think there are many two player games out there. This will not be two playing at same time but one player solving the board then another player getting the same board and trying to beat the time or each player designing a board for the other to see who can screw the other one up the worst.

MigLayout Verification plug-in for IntelliJ - what it was like to write it

I am happy to report I have written my first plug-in for IntelliJ. It gets around the issues I reported in a previous post about MigLayout using strings for constraints and those strings are validated at run time. This puts in you the fun situation of writing code, running it, getting an error, fixing it and repeating until you have the constraint syntax correct.

That is no longer an issue if you use the MigLayout Verification plug-in for IntelliJ. I have submitted it to Jet Brains but have not heard back from them as of yet. You can grab it and manually install it from the MigLayout form if you want to give it a shot. Download from MigLayout forum. This will be moved to a permanent spot on his main page soon and hopefully available from JetBrains too.

If you look at the forum you can see the author of MigLayout is an IntelliJ user and is using the plug-in himself. I needed to contact him as I am using MigLayout code in my plug-in and I wanted to make sure that was OK with him per his license agreement. He was very kind and allowed me to use the code meaning the parse logic and errors you get are the exact same as the ones you would get at run time.

What does the plug-in do for you? It allows you to highlight the lines of text in your Java code that use MigLayout, press Alt+Shift+Y and it will find every constraint error in the code showing them to you in a console view. You can click on each error to have it pop to that line of code highlighting the string in error so you can fix it. Notice it is every error so you can quickly clean them all up. It handles layout, row, column and component constraints.

On a side note I am happy I did not try to name this blog based on a language. A lot of blogs are C# Corner or Java Jabbering etc. Since I end up all over the place - Android, iPhone, Java, C#, plug-ins, etc. that would have been pretty misleading.

What was it like to develop an IntelliJ plug-in?

Not easy which is a shame. The big issue was the lack of documentation. I figured IntelliJ had been out for a long time, they are in the process of finalizing the version 10 release and there appear to be a lot of plug-ins so there must be web sites about it and various bits of on-line help. That just was not the case. The only help I found was directly on the IntelliJ forums and a lot of that was many years out of date. See very bottom of this post for code snippets to solve the issues I mention.

I wrote the guts of the program using a MigLayout based test program. A basic text area with a button to parse and a listbox with the errors I found. I needed to get the code processing in place first as I have to skip comments, combine lines of code that span multiple lines, split out the string pieces, do RegEx processing and guess if it really is constraint etc. No need to do all that work with the plug-in overhead. Once I had that running I contacted the MigLayout author to see how he felt about things.

The initial plug-in documentation seemed pretty easy, set up an action, query the editor and parse. I have written Eclipse plug-ins in the past, in fact it was a whole RCP, so I remember how involved that can be. Very powerful but very involved with XML things here and linking three files perfectly via string names to tie it together. This was going to be so much easier.

First thing you can't just developer a plug-in using the out of the box community edition of IntelliJ. You have to install GIT and download the full source project. This is 1g worth of data. It failed on me the first two times. I had to make sure my machine did not come anywhere near going to sleep during the process. After that initial 1g hit was another 1/2g of deltas that got automatically downloaded. GIT is an all or nothing download so it does not restart in the middle if things to awry. It takes a really long time to download. Finally you have to build the program with a simple ant build script. No problem if you have ant installed and configured which I did not but that was easy to do.

It then creates a ZIP file for Windows and the proper images for the other support OS platforms. Honestly if would be nice if they just posted the 80 meg zip file I needed for Windows. This is a fully functional version of IntelliJ along with plug-in SDK support that you install.

You point your running instance to this new instance as the plug-in SDK and you can start code development. So far so good but there is very little sample code to get you started. I did a lot of source code scrounging and using the little bit of on-line help I could fine. I was able to figure out how to get the project, editor and document so I could pull out the selected lines. I found a way to determine what line number I was on and I could run my already functional parser and show the errors in an alert dialog. I wanted a console window.

Others have asked on the forums about the console window and I pieced together various posts to get it working but all I could output was straight text lines. I wanted you to be able to click on them to jump to the spot in the code of the issue. Others had asked the question with no answers. I did my first release to the developers at work without that support. In the end I did figure out how to use the printHyperlink call to do what I wanted.

Hot keys threw me off for a bit too. I misread the dialog and thought you put in two hot keys, a primary and a secondary but it really meant if you put in two the user would have to do them in order. I wanted different hot keys for the default key mapping vs. the Eclipse key mapping I use. I did not figure out how to do that so I finally found Alt+Shift+Y that was free in both mappings. Since access to the feature also appears in the Code menu you can still use it even if that key sequence is not available in your mapping.

Even getting to the name of the currently edited file was not something I was able to find on the forums. I pieced together how to do that too. While doing that I realized there were various deprecation warnings. I like my code to be warning free so I went about fixing those. Remember I said a lot of forum post are years old, many from 2004. I was able to clean up all the warnings and the new code looked better and is easier to read. One clean up just created new deprecation warnings around the console view. I was finally able to piece all that together and ended up with warning free.

The hyperlink stuff into the console window was confusing. Each hyperlink you create needs to implement an interface but the only call you get is public void navigate(Project project). You don't even get the text they clicked on! I was hoping to have one generic link click listener but instead I had to create a new object that implements the interface taking the text of the link in the constructor. From there I had to figure out all the editor document access to pull lines and selection model processing to scroll to and highlight the errors.

I am very happy with the end results and will get a lot of use out of the plug-in as I am sure others in the office and hopefully you will too. I don't have a good code formatter for blogspot so below are my code chunks that may help other plug-in developers.

UPDATE: The plug-in has been accepted by IntelliJ and is available from their website.  MigLayout Verifier at JetBrains

Imports I am using for all of the below

import com.intellij.execution.filters.TextConsoleBuilder;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;


Creating an associated console view in actionPerformed(AnActionEvent e) method



  if (project != null) {
      ToolWindowManager manager = ToolWindowManager.getInstance(project);
      ToolWindow window = manager.getToolWindow(id);
      if (window == null) {
          TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance();
          TextConsoleBuilder builder = factory.createBuilder(project);
          view = builder.getConsole();
          window = manager.getToolWindow(id);

          if (window == null) {
              window = manager.registerToolWindow(id, true, ToolWindowAnchor.BOTTOM);
              ContentFactory contentFactory = ServiceManager.getService(ContentFactory.class);
              Content content = contentFactory.createContent(view.getComponent(), "", false);
              window.getContentManager().addContent(content);
              window.show(new Runnable(){
                  public void run() {
                      // Nothing for us to do here
                  }
              });
          }
      }
  }


Jumping to a line of code and highlighting text on that line


public void actionPerformed(AnActionEvent e) {
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  String findMe = "Text to find on line"
  int line = {line in document you are processing - 1 based}

  int lineStart = editor.getDocument().getLineStartOffset(line - 1);
  int lineEnd = editor.getDocument().getLineEndOffset(line - 1);

  TextRange textRange = new TextRange(lineStart, lineEnd);

  String lineText = editor.getDocument().getText(textRange);
  int pos = lineText.indexOf(findMe);
  if (pos != -1) {
      SelectionModel selModel = editor.getSelectionModel();
      selModel.setSelection(lineStart + pos, lineStart + pos + findMe.length());
      ScrollingModel scrollModel = editor.getScrollingModel();
      scrollModel.scrollTo(new LogicalPosition(line - 1, col), ScrollType.MAKE_VISIBLE);
  }

Getting file name of active editor

public void actionPerformed(AnActionEvent e) {
  VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
  String fileName = virtualFile.getName()

Getting selected text and line where it starts

public void actionPerformed(AnActionEvent e) {
  Editor editor = e.getData(PlatformDataKeys.EDITOR);
  SelectionModel selModel = editor.getSelectionModel();

  int curLine = editor.getDocument().getLineNumber(selModel.getSelectionStart());
  String selText = selModel.getSelectedText();