Shortlink

JSON data from NodeJS into iPhone app

Given below is a simple example of loading JSON data from a Node JS application into an iPhone application. The JSON data is serialized into a collection class.

The Node JS application is a http server implementation that returns a collection of countries in JSON format as response. The server.js file which can be run as “node server.js” from terminal window is shown below.

//server.js
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('[{"name":"India","population":1212121213222},{"name":"USA","population":889632232}]');
}).listen(9090, '127.0.0.1');
console.log('Server running at http://127.0.0.1:9090/');

The Node JS application runs at http:127.0.0.1:9090/. The Objective-C code that sends a request to this URL, that loads the JSON data and deserializes it to a collection object is given below.

   NSURL* url = [[NSURL alloc]initWithString:@"http://127.0.0.1:9090/"];
   NSData* nsdata = [[NSData alloc]initWithContentsOfURL:url];
   NSError* error;
   id obj =[NSJSONSerialization JSONObjectWithData:nsdata options:kNilOptions error:&error];
  
   if([obj isKindOfClass:[NSDictionary class]]){
        NSDictionary* dict = obj;
        NSLog(@"Count: %d",[dict count]);
        NSLog(@"Name: %@, Population: %@",[dict objectForKey:@"name"],[dict objectForKey:@"population"]);
   }
   else{
        NSArray* arr = obj;
        for (int i=0; i<[arr count]; i++) {
            NSDictionary* dict = [arr objectAtIndex:i];
            NSLog(@"Name: %@, Population: %@",[dict objectForKey:@"name"],[dict objectForKey:@"population"]);
        }
  }

In the application that we were building, the JSON data was either an array of countries or just a country instance alone. For this reason, you can see the use of ‘isKindOfClass‘ function used to cast the JSON data to NSArray or NSDictionary object.

Shortlink

TDD in iPhone applications

One aspect of development of apps for iOS devices, that has surprised me is developers ignoring the unit testing capabilities of Objective-C. Most of the developers who develop apps on iPhone ignore the testcase files that are created while creating new classes. Some peacefully uncheck the option of creating test-cases as well. This is not the case with Java/.NET/Ruby/Groovy application developers, however.

Objective-C applications can be developed using Test Driven Development like the other counterparts. You’ve SenTestingKit API and mock object frameworks like ocmock. You have the same assertXXX(in this case STAssertXXX functions) functions, setup, teardown etc., here as well. It just requires a discipline to use them.

We were playing with collection classes in Objective-C, so decided to implement a TicTacToe game on the iPhone. The TicTacToe game logic was developed in a class “TicTacToeEngine” using TDD and the UI was developed later. The TicTacToeEngineTests file(s) is shown below. The TicTacToeEngineTests.h gives you the list of tests in the file. It serves as a good documentation.

//TicTacToeEngineTests.h
#import <SenTestingKit/SenTestingKit.h>
#import "TicTacToeEngine.h"

@interface TicTacToeTests : SenTestCase

@property TicTacToeEngine* tictactoeEngine;

//test methods
-(void)testTicTacToeEngineNotNull;
-(void)testTicTacToeEngineInitializedWith9Squares;
-(void)testGameOverIsFalseInTheBeginning;
-(void)testInitialPlayerIsO;
-(void)testStartGameByPlacingOAtAPosition;
-(void)testPlayGameByPlacingXAtAPosition;
-(void)testCheckIfXIsGettingPlaced;
-(void)testCheckPlacePegAtAlreadyOccupiedPosition;
-(void)testPlayGameWithAlternatePlayersOccupyingPosition;
-(void)testGetUnoccupiedPosition;
-(void)testGameOverIfRows1And2And3Match;
-(void)testGameOverIfRows7And8And9Match;
-(void)testGameOverIfColumns1And4And7Match;
-(void)testGameOverIfColumns2And5And8Match;
-(void)testGameOverIfColumns3And6And9Match;
-(void)testGameOverIfDiagonals3And5And7Match;
-(void)testPlayAfterGameIsOver;
@end
//TicTacToeEngineTests.m

#import "TicTacToeEngineTests.h"
#import "TicTacToeEngine.h"

@implementation TicTacToeTests

@synthesize tictactoeEngine;
- (void)setUp
{
    [super setUp];
    tictactoeEngine = [[TicTacToeEngine alloc]init];
}

- (void)tearDown
{
    tictactoeEngine = nil;
    [super tearDown];
}
-(void)testPlayAfterGameIsOver{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:3];
    [tictactoeEngine placePegAt:6];
    [tictactoeEngine placePegAt:9];
    STAssertThrows([tictactoeEngine placePegAt:7], @"Game is already over");
}
-(void)testGameOverIfDiagonals3And5And7Match{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:2];
    [tictactoeEngine placePegAt:4];
    [tictactoeEngine placePegAt:6];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"C"],@"Game is over");
}
-(void)testGameOverIfColumns3And6And9Match{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:3];
    [tictactoeEngine placePegAt:6];
    [tictactoeEngine placePegAt:9];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"X"],@"Game is over");
}
-(void)testGameOverIfColumns2And5And8Match{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:2];
    [tictactoeEngine placePegAt:5];
    [tictactoeEngine placePegAt:8];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"X"],@"Game is over");
}
-(void)testGameOverIfColumns1And4And7Match{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:2];
    [tictactoeEngine placePegAt:5];
    [tictactoeEngine placePegAt:6];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"C"],@"Game is over");
}
-(void)testGameOverIfRows7And8And9Match{
    [tictactoeEngine start];//1
    [tictactoeEngine placePegAt:7];
    [tictactoeEngine placePegAt:3];
    [tictactoeEngine placePegAt:8];
    [tictactoeEngine placePegAt:9];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"X"],@"Game is over");
}
-(void)testGameOverIfRows1And2And3Match{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:4];
    [tictactoeEngine placePegAt:6];
    STAssertTrue(tictactoeEngine.gameOver,@"Game is over");
    STAssertTrue([tictactoeEngine.winner isEqualToString:@"C"],@"Game is over");
}

-(void)testGetUnoccupiedPosition{
    [tictactoeEngine start];
    int position = [tictactoeEngine getUnoccupiedPosition];
    STAssertTrue(position == 2, @"Position should be 2, and it is %d",position);
}
-(void)testPlayGameWithAlternatePlayersOccupyingPosition{
    [tictactoeEngine start];
    int oposition = [tictactoeEngine placePegAt:2];
    STAssertTrue([[tictactoeEngine getPegAt:oposition] isEqualToString:@"C"], @" Value at 3 is-%@*",[tictactoeEngine getPegAt:oposition]);
    oposition = [tictactoeEngine placePegAt:6];
    STAssertTrue([[tictactoeEngine getPegAt:oposition] isEqualToString:@"C"], @" Value at 4 is-%@*",[tictactoeEngine getPegAt:oposition]);

}
-(void)testCheckPlacePegAtAlreadyOccupiedPosition{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:2];
    STAssertThrows([tictactoeEngine placePegAt:2], @"Exception should be thrown");
}

-(void)testCheckIfXIsGettingPlaced{
    [tictactoeEngine start];
    [tictactoeEngine placePegAt:2];
    [tictactoeEngine placePegAt:4];
    
    STAssertTrue([[tictactoeEngine getPegAt:1] isEqualToString:@"C" ], @"Peg at squarePosition 1 is C");
    STAssertTrue([[tictactoeEngine getPegAt:2] isEqualToString:@"X" ], @"Peg at squarePosition 2 is X");
    STAssertTrue([[tictactoeEngine getPegAt:4] isEqualToString:@"X" ], @"Peg at squarePosition 4 is X");
}

-(void)testPlayGameByPlacingXAtAPosition{
    int squarePosition = 2;
    [tictactoeEngine placePegAt:squarePosition];
    STAssertTrue([[tictactoeEngine getPegAt:squarePosition] isEqualToString:@"X" ], @"Peg at squarePosition 2 is X");
}


-(void)testStartGameByPlacingOAtAPosition{
    int squarePosition = 1;
    [tictactoeEngine start];
    STAssertTrue([[tictactoeEngine getPegAt:squarePosition] isEqualToString:@"C" ], @"Peg at squarePosition 1 is C");
}

-(void)testInitialPlayerIsO{
    NSLog(@"%@",tictactoeEngine.currentPlayer);
    STAssertTrue([tictactoeEngine.currentPlayer isEqualToString:@"C"], @"Initial player is C");
}
-(void)testGameOverIsFalseInTheBeginning{
    STAssertFalse(tictactoeEngine.gameOver, @"GameOver should be false");
}

-(void)testTicTacToeEngineInitializedWith9Squares{
    STAssertTrue([tictactoeEngine.squares count] == 9, @"TicTacToeEngine squares are 9");
}
-(void)testTicTacToeEngineNotNull{
    STAssertNotNil(tictactoeEngine, @"TicTacToeEngine is not null");
}
@end

The TicTacToeEngine class that evolved out of TDD approach is shown below.

//TicTacToeEngine.h
@interface TicTacToeEngine : NSObject

@property NSMutableDictionary* squares;
@property bool gameOver;
@property NSString* currentPlayer;
@property NSString* winner;

-(TicTacToeEngine*)init;
-(void)start;
-(NSString*)getPegAt:(int)position;
-(int)placePegAt:(int)position;
-(int)getUnoccupiedPosition;
@end
//TicTacToeEngine.m
@implementation TicTacToeEngine
@synthesize squares;
@synthesize gameOver;
@synthesize currentPlayer;
@synthesize winner;

-(int)getUnoccupiedPosition{
    int position = -1;
    for (int i=1; i<=[squares count]; i++) {
        NSString* key = [[NSString alloc]initWithFormat:@"%d",i];
        NSString* val = [squares objectForKey:key];
        if([val isEqualToString:@""]){
            position = i;
            break;
        }
    }
    return position;
}
-(void)checkRows7And8And9{
    NSString* keyA = [[NSString alloc]initWithFormat:@"%d",7];
    NSString* valA = [squares objectForKey:keyA];
    NSString* keyB = [[NSString alloc]initWithFormat:@"%d",8];
    NSString* valB = [squares objectForKey:keyB];
    NSString* keyC = [[NSString alloc]initWithFormat:@"%d",9];
    NSString* valC = [squares objectForKey:keyC];
    if(!([valA isEqualToString:@""] || [valB isEqualToString:@"" ] || [valC isEqualToString:@""])){
        if([valA isEqualToString:valB] && [valB isEqualToString:valC]){
            gameOver = true;
        }
    }
}

-(void)checkRows4And5And6{
    NSString* keyA = [[NSString alloc]initWithFormat:@"%d",4];
    NSString* valA = [squares objectForKey:keyA];
    NSString* keyB = [[NSString alloc]initWithFormat:@"%d",5];
    NSString* valB = [squares objectForKey:keyB];
    NSString* keyC = [[NSString alloc]initWithFormat:@"%d",6];
    NSString* valC = [squares objectForKey:keyC];
    if(!([valA isEqualToString:@""] || [valB isEqualToString:@"" ] || [valC isEqualToString:@""])){
        if([valA isEqualToString:valB] && [valB isEqualToString:valC]){
            gameOver = true;
        }
    }
}

-(void)checkCellsMatch:(int)cellA :(int)cellB :(int)cellC{
    NSString* keyA = [[NSString alloc]initWithFormat:@"%d",cellA];
    NSString* valA = [squares objectForKey:keyA];
    NSString* keyB = [[NSString alloc]initWithFormat:@"%d",cellB];
    NSString* valB = [squares objectForKey:keyB];
    NSString* keyC = [[NSString alloc]initWithFormat:@"%d",cellC];
    NSString* valC = [squares objectForKey:keyC];
    if(!([valA isEqualToString:@""] || [valB isEqualToString:@"" ] || [valC isEqualToString:@""])){
        if([valA isEqualToString:valB] && [valB isEqualToString:valC]){
            gameOver = true;
        }
    }
}
-(void)checkGameIsOver{
    [self checkCellsMatch:1 :2 :3];
    [self checkCellsMatch:4 :5 :6];
    [self checkCellsMatch:7 :8 :9];

    
    [self checkCellsMatch:1 :4 :7];
    [self checkCellsMatch:2 :5 :8];
    [self checkCellsMatch:3 :6 :9];
    
    [self checkCellsMatch:1 :5 :9];
    [self checkCellsMatch:3 :5 :7];
}

- (int)placeO {
    NSString *key;
    int oposition = [self getUnoccupiedPosition];
    key = [[NSString alloc]initWithFormat:@"%d",oposition];
    [squares setObject:@"C" forKey:key];
    [self checkGameIsOver];
    if(gameOver)
        winner = @"C";
    return oposition;
}

-(int)placePegAt:(int)position{
    int oposition = -1;
    if(gameOver)
        [TicTacToeException raise:@"Game is over" format:@"Game is already over"];
    
    NSString* key = [[NSString alloc]initWithFormat:@"%d",position];
    NSString* val = [squares objectForKey:key];
    if([val isEqualToString:@"X"] || [val isEqualToString:@"C"])
        [TicTacToeException raise:@"Already occupied position" format:@"Cannot place peg at position %d",position];
    [squares setObject:@"X" forKey:key];
    [self checkGameIsOver];
    if(!gameOver){
        oposition = [self placeO];
    }
    else{
        winner = @"X";
    }
    return oposition;
}

-(void)start{
    [squares setObject:@"C" forKey:@"1"];
}
-(NSString*)getPegAt:(int)position{
    NSString* key = [[NSString alloc]initWithFormat:@"%d",position];
    return [squares objectForKey:key];
}
-(TicTacToeEngine*)init{
    self = [super init];
    self.gameOver = false;
    self.currentPlayer = @"C";
    self.squares = [[NSMutableDictionary alloc]initWithObjectsAndKeys:
                    @"",@"1",
                    @"",@"2",
                    @"",@"3",
                    @"",@"4",
                    @"",@"5",
                    @"",@"6",
                    @"",@"7",
                    @"",@"8",
                    @"",@"9"
                    , nil];
    return self;
}
@end
Shortlink

MVP example in GWT

GWT applications are designed using Model-View-Presenter (MVP) pattern. You can find a sample MVP application at https://github.com/prabhu-durasoft/GWTMVPExample1/

The MVP example has been slightly tweaked. The application throws up a textbox where you can enter a name of a country. On hitting the button, you will have the capital fetched from a remote servlet and displayed.

Shortlink

Charts in GXT 3

You can find a basic pie and bar chart implementation in https://github.com/prabhu-durasoft/ChartGXT3 in GXT 3. The example displays the charts as shown below.

Shortlink

Basic Grid using UiBinder in GXT3

UiBinder in GWT provides you a facility for declarative UI. The UI is defined in a xml file and the code associated with the UI is defined in a separate java file.

You can find a grid example in GXT3 implemented using UiBinder at https://github.com/prabhu-durasoft/BasicGridUIBinderGXT3. The Grid displays a list of countries. The UiBinder xml file, with the grid declaration is shown below.

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui"
	xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid">
	<ui:with type="com.sencha.gxt.widget.core.client.grid.ColumnModel" field="countryColumnModel"></ui:with>
	<ui:with type="com.sencha.gxt.widget.core.client.grid.GridView" field="countryGridView"></ui:with>
	<ui:with type="com.sencha.gxt.data.shared.ListStore" field="countryStore"></ui:with>
	<g:HTMLPanel>
		<grid:Grid ui:field="countryGrid" cm="{countryColumnModel}" 
					store="{countryStore}" view="{countryGridView}" 
					borders="true" height="300" width="302">
		</grid:Grid>
	</g:HTMLPanel>
</ui:UiBinder>