While the current lightness and versatility of the j2me app is satisfactory, there still seems to be one Achilles's heel that spoils the deal for the rest. User registration has the greatest friction, and considering its position in the use-hierarchy of the app, has the potential for massive drop-off.
Best possible solution for tackling this problem has been detailed by Chinedu here http://sayaj2me.blogspot.com/2013/07/real-world-testing.html?showComment=1374598239927#c8073988122514701011
This leaves the problem of how to send international SMSes which usually attract ridiculous charges, especially in the developing world(politically correct term :) . So far in my research, the solution lies in creating a much more complex registration infrastructure but I believe the dividends will pay off.
This involves the 'buying' of local numbers and hosting them on a central internet-available server. Therefore, the app will only send to that user's local number hence charging him a local rate for text(acceptable). The hosted number calls the Saya server with requisite parameters and the registration cycle kicks off.
Currently, number hosting will cost around $100 per number per month. We can start by leveraging on the countries with the most potential users and build out from there.
Saturday, August 3, 2013
Tuesday, July 23, 2013
Real World Testing
Saya is greatly liked by real world
people who have sampled it. Once logged in and inside the app,
everything is easy and straight forward to use. However, there are
some pain points that pose a real threat to loosing out on users:
- Registration process – This is by far the most painful part of the app. It's tedious and some users do not understand why it's necessary(coupled with the instinctive feeling of avoiding to give out their phone numbers aimlessly). Most users are confused about the applicability of the received SMS and what to do with it(some I observed reply to that SMS with the code :) . All in all, the general verdict was that a way should be found that takes away registration from the app. *Possible solution illuminated below
- Notifications – On new apps there are 2 notifications. On the 2nd one “You currently have no friends. Please Invite or Sync.”, most users gloss over this and choose their required action(*Reading phonebook past 215 contacts seems to be a problem—troubleshooting still continuing). The first notification “Saya may collect device info to enhance experience.” causes relatively more confused than the former. Users have been observed to select the “Read more” option more than “Dismiss”. Good news however is that all this options have a good 'dead end' and the user cannot possibly stray out of the flow.
*Possible Solution to the 1st
problem
We can provide an online portal where
the user handles the authentication there. I.E
a) Ask for user number
b) Send user code SMS
c) Ask user for the code to
authenticate it's his number
d) Send link of prepackaged app to his
number
Concentrating on the last option(d),
the app can be prepackaged in a number of ways
- Recompile on the fly with the number hardwired in the app. This unfortunately is not possible for a signed app, otherwise you would have to sign the app every time it's recompiled.
- Add the user number as a field in the JAD file.
Wednesday, July 17, 2013
The History Of Ctrl + Alt + Delete
In the spring of 1981, David Bradley was part of a select team working from a nondescript office building in Boca Raton, Fla. His task: to help build IBM’s new personal computer. Because Apple and RadioShack were already selling small stand-alone computers, the project (code name: Acorn) was a rush job. Instead of the typical three- to five-year turnaround, Acorn had to be completed in a single year.
One of the programmers’ pet peeves was that whenever the computer encountered a coding glitch, they had to manually restart the entire system. Turning the machine back on automatically initiated a series of memory tests, which stole valuable time. “Some days, you’d be rebooting every five minutes as you searched for the problem,” Bradley says. The tedious tests made the coders want to pull their hair out.
So Bradley created a keyboard shortcut that triggered a system reset without the memory tests. He never dreamed that the simple fix would make him a programming hero, someone who’d someday be hounded to autograph keyboards at conferences. And he didn’t foresee the command becoming such an integral part of the user experience.
Bradley joined IBM as a programmer in 1975. By 1978, he was working on the Datamaster, the company’s early, flawed attempt at a PC. It was an exciting time—computers were starting to become more accessible, and Bradley had a chance to help popularize them.
In September 1980, he became the 12th of 12 engineers picked to work on Acorn. The close-knit team was whisked away from IBM’s New York headquarters. “We had very little interference,” Bradley says. “We got to do the design essentially starting with a blank sheet of paper.”
Bradley worked on everything from writing input/output programs to troubleshooting wire-wrap boards. Five months into the project, he created ctrl+alt+del. The task was just another item to tick off his to-do list. “It was five minutes, 10 minutes of activity, and then I moved on to the next of the 100 things that needed to get done,” he says. Bradley chose the keys by location—with the del key across the keyboard from the other two, it seemed unlikely that all three would be accidentally pressed at the same time. Bradley never intended to make the shortcut available to customers, nor did he expect it to enter the pop lexicon. It was meant for him and his fellow coders, for whom every second counted.
The team managed to finish Acorn on schedule. In the fall of 1981, the IBM PC hit shelves—a homely gray box beneath a monitor that spit out green lines of type. Marketing experts predicted that the company would sell a modest 241,683 units in the first five years; company execs thought that estimate was too optimistic. They were all wrong. IBM PC sales would reach into the millions, with people of all ages using the machines to play games, edit documents, and crunch numbers. Computing would never be the same.
And yet, few of these consumers were aware of Bradley’s shortcut quietly lingering in their machines. It wasn’t until the early 1990s, when Microsoft’s Windows took off, that the shortcut came to prominence. As PCs all over the country crashed and the infamous “blue screen of death” plagued Windows users, a quick fix spread from friend to friend: ctrl+alt+del. Suddenly, Bradley’s little code was a big deal. Journalists hailed “the three-finger salute” as a saving grace for PC owners—a population that kept growing.
In 2001, hundreds of people packed into the San Jose Tech Museum of Innovation to commemorate the 20th anniversary of the IBM PC. In two decades, the company had moved more than 500 million PCs worldwide. After dinner, industry luminaries, including Microsoft chairman Bill Gates, sat down for a panel discussion. But the first question didn’t go to Gates; it went to David Bradley. The programmer, who has always been surprised by how popular those five minutes spent creating ctrl+alt+del made him, was quick to deflect the glory.
“I have to share the credit,” Bradley joked. “I may have invented it, but I think Bill made it famous.”
Tuesday, July 16, 2013
Another Pesky issue
Been working on monitoring 'Connection Lost' and having opted to go with 'Heartbeat' method( http://stackoverflow.com/a/486695/1048839 ), I stumbled upon a pesky problem that was stubborn enough to have Mawuli convince me to reboot my laptop(1st time in 2 months). Even then, the problem persisted.
To handle new app updates, the app listens for updated from the server and acts accordingly.
The problems occurs in the small sleeping thread for the last 2 options as it counts from 10 down to one. On the 3rd count(i.e no.7), it fires the cmd_update by itself and therefore tries to execute an update. The solution, weirdly enough, I found was to add another command to the alert and that stopped cmd_update from firing.
To handle new app updates, the app listens for updated from the server and acts accordingly.
public void handleNotify(JSONObject json) throws JSONException{
JSONObject json_obj = json.getJSONObject("result"); //Check for update variable if(json_obj.getString("type").equals("update")){System.out.println("Update Found for..."+app_version); String[] latest_version = split(json_obj.getString("ver"), "."); String[] current_version = split(app_version, ".");
//Assign new verion number to app_version variable to avoid future //checks during current session app_version = json_obj.getString("ver"); //Compare versions and enable update download if(!latest_version[0].equals(current_version[0])){ //Force download update cmd_update = new Command("Update", Command.OK, 0); cmd_exit = new Command("Exit", Command.EXIT, 1); Alert alert = new Alert("Update", "New Update available.\nPlease Download", null, AlertType.CONFIRMATION); alert.addCommand(cmd_update); alert.addCommand(cmd_exit); alert.setCommandListener(this);
Display.getDisplay(this).setCurrent(alert);
//Sleep midlet try {
Thread.sleep(100000); //Force App close instance.notifyDestroyed(); } catch (InterruptedException ex) { ex.printStackTrace(); } }else if(!latest_version[1].equals(current_version[1])){ //Ask to update cmd_update = new Command("Update", Command.OK, 0);
Alert alert = new Alert("Update", "New Update available.\nPlease Download", null, AlertType.CONFIRMATION); alert.addCommand(cmd_update);
alert.setCommandListener(this);
Display.getDisplay(this).setCurrent(alert);
//Sleep midlet try { for(int i=10; i>0; i--){
alert.setString("["+Integer.toString(i) +"] "+"New Update available.\nPlease Download"); Thread.sleep(1000); }
} catch (InterruptedException ex) { ex.printStackTrace(); } }else if(!latest_version[2].equals(current_version[2])){ //Ask to update cmd_update = new Command("Update", Command.OK, 0);
Alert alert = new Alert("Update", "New Update available.\nPlease Download", null, AlertType.CONFIRMATION);
alert.addCommand(cmd_update);
alert.setCommandListener(this);
Display.getDisplay(this).setCurrent(alert);
//Sleep midlet try { for(int i=10; i>0; i--){
alert.setString("["+Integer.toString(i) +"] "+"New Update available.\nPlease Download"); Thread.sleep(1000); }
} catch (InterruptedException ex) { ex.printStackTrace(); } }
}
}//--End of handleNotify(JSONObject);
//Ask to update
cmd_update = new Command("Update", Command.OK, 0);
cmd_exit = new Command("Exit", Command.EXIT, 1);
Alert alert = new Alert("Update", "New Update available.\nPlease Download", null, AlertType.CONFIRMATION);
alert.addCommand(cmd_update);
alert.addCommand(cmd_exit);
alert.setCommandListener(this);
Display.getDisplay(this).setCurrent(alert);
Wednesday, July 10, 2013
Might have lost the battle but the war is still on
36 hours later and I still can't figure out what causes "Unhandled Exception" just after handling notify for app update(handleNotify()).
So I decided to yield(for now) and re-write my code around the problem. Effectively, updates will only be handled for a registered app. They will not he handled for the first session of the app where registration is carried. They will only be handled for subsequent app sessions thereafter. See attached code.
P/S: Update is handled according to Chinedu's prescribed handling techniques i.e for version x.y.z
where..
x denotes major release hence force an update(no grace. App timeout&exit after 100 secs)
y denotes a feature release hence ask user to update(10 sec grace applied on screen)
z denotes a bug fix hence ask user to update(10 sec grace applied on screen)
So I decided to yield(for now) and re-write my code around the problem. Effectively, updates will only be handled for a registered app. They will not he handled for the first session of the app where registration is carried. They will only be handled for subsequent app sessions thereafter. See attached code.
//Check for updates
try {
JSONObject json = new JSONObject(aPacket.getUTF8());
if(json.getString("mod").equals("notify")){
try {
//Check for bearer to determine if app is logged in
if(packet_handler.getBearer() != null){
handleNotify(json);
}
} catch (RecordStoreException ex) {
ex.printStackTrace();
}
}
} catch (JSONException ex) {
ex.printStackTrace();
}
P/S: Update is handled according to Chinedu's prescribed handling techniques i.e for version x.y.z
where..
x denotes major release hence force an update(no grace. App timeout&exit after 100 secs)
y denotes a feature release hence ask user to update(10 sec grace applied on screen)
z denotes a bug fix hence ask user to update(10 sec grace applied on screen)
Tuesday, July 9, 2013
Handling dead URLs
Came across this problem when handling an improbable dead url; http://api.saya.im/locbyip which is the Location By IP API. Because I always assumed it will give back something, I failed to handle the odd case of the url being dead.
This can be done with a small one-liner addition
throw new IOException("Response:"+http.getResponseCode()+", "+http.getResponseMessage());
Here's the full code of the openUrl method
This can be done with a small one-liner addition
throw new IOException("Response:"+http.getResponseCode()+", "+http.getResponseMessage());
Here's the full code of the openUrl method
public String openUrl(String url) throws IOException, SecurityException{
byte[] data = null;
HttpConnection http = (HttpConnection)Connector.open(url, Connector.READ_WRITE, true);
//set custom headers
http.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.1");
http.setRequestProperty("Accept_Language", "en-US");
//Read response
if(http.getResponseCode() == HttpConnection.HTTP_OK){
int len = (int)http.getLength();
is = http.openInputStream();
if(is == null){
throw new IOException("Cannot open HTTP InputStream, Aborting..");
}
//Else continue reading stream
response = "";
Reader in = new InputStreamReader(is);
StringBuffer temp = new StringBuffer(1024);
char[] buffer = new char[1024];
int read;
while((read = in.read(buffer, 0, buffer.length)) != -1){
temp.append(buffer, 0, read);
}
response = temp.toString();
}else{
System.out.println("Response:"+http.getResponseCode()+", "+http.getResponseMessage());
response = null;
throw new IOException("Response:"+http.getResponseCode()+", "+http.getResponseMessage());
}
if(http != null){
http.close();
}
if(is != null){
is.close();
}
return response;
}//--End of openUrl()
Monday, June 17, 2013
Private Chat packets(reversal)
While it makes sense, it's worth noting that when A sends a private chat to B, the 'from' variable = A and 'room' variable = B. This also happens vice-versa. The issue here is that since ALL messages are broadcasted across the users, then 'room' value keeps changing. Sample this
1. Broadcast message from me
2. Broadcast message from friend with reply
As seen, the 'room' variable keeps changing. To handle this change:
1. Check if chat is private
2. Check that chat is not from me
3. Apply room reversal
1. Broadcast message from me
processPacket...{"ref":"chat","mod":"chat","status":200,"err":null,"result":{"type":"p","ctype":"t","id":"2E3QW1AW34ay7y410wfQ","msg":"Hey, please send another message","from":"254712249559","room":"233200238442","coord":null,"ts":"1371461833"}}
Reading data...
2. Broadcast message from friend with reply
processPacket...{"ref":"3_faaa586c9aa38f01968715d762e3c995","mod":"chat","status":200,"err":null,"result":{"type":"p","ctype":"t","id":"2E3QWagtWQM0RYWluxOK","msg":"another message","from":"233200238442","room":"254712249559","coord":"5.645256:-0.151525","ts":"1371461857"}}
Reading data...
As seen, the 'room' variable keeps changing. To handle this change:
1. Check if chat is private
2. Check that chat is not from me
3. Apply room reversal
/*
* Handle reversal for private chat--arghh!!!!
*
*/
if(json.getJSONObject("result").getString("type").equals("p")){
//Check if message is not from me and reverse roomid
if(!json.getJSONObject("result").getString("from").equals(phone)){
roomid = json.getJSONObject("result").getString("from");
}
}
java.io.IOException: error 32 during TCP write
On netbeans project.Right click on project->properties->manage emulators->tools & extensions-> and changing the Http Version from HTTP1.1 to HTTP1.0. This works in Sun Wireless Toolkit 2.5.2.
Saturday, June 15, 2013
Cleaning up final app
Been cleaning, tweaking and generally finalizing the app for submition. One think pointed out by Mawuli is the fact that non-English speakers will find it hard to use the app and therefore am customizing to accommodate them.
One of the changes revolves around the registration process where the (English)language is being simplified and also the process made more fool proof by providing a means for the user to choose which country they are in. This brings onboard a some complexities,namely file size increase and code refactoring.
I've attached a CSV file(countrycodes.txt) as a resource in the images folder which contains international dialing codes for all countries. The file has increased the overall app size by 3.5kb.
Since users are not mandated to append their international dialing codes to their numbers, I have to curate the number and country chosen so as to come up with a valid mobile number. Below is the guidance am employing in this task.
//www.digitalsms.net/faq/numberformat/index.php
One of the changes revolves around the registration process where the (English)language is being simplified and also the process made more fool proof by providing a means for the user to choose which country they are in. This brings onboard a some complexities,namely file size increase and code refactoring.
I've attached a CSV file(countrycodes.txt) as a resource in the images folder which contains international dialing codes for all countries. The file has increased the overall app size by 3.5kb.
Since users are not mandated to append their international dialing codes to their numbers, I have to curate the number and country chosen so as to come up with a valid mobile number. Below is the guidance am employing in this task.
All mobile numbers need to be entered in this format.
Coutry Code, Network prefix (no leading 0), Mobile Number.
For example, the mobile number 072 244 3259 in South Africa would be formatted like this:

//www.digitalsms.net/faq/numberformat/index.php
Monday, May 20, 2013
New Changes
Great thanks @Chinedu and @Badu for porting the Android ws for me to j2me.
Just finished rebuilding new the app again. Structure is more or less the same except with some changes in the login process. Login now handled by a new class, loginClass. Reused RMS read and write procedures. Great design where everything stored in the RMS is based on a hashlist, hence enabling quick access to values. RMS methods are part of the sayaConnection class.
More to come...
Just finished rebuilding new the app again. Structure is more or less the same except with some changes in the login process. Login now handled by a new class, loginClass. Reused RMS read and write procedures. Great design where everything stored in the RMS is based on a hashlist, hence enabling quick access to values. RMS methods are part of the sayaConnection class.
More to come...
Wednesday, April 10, 2013
Happy as a bald Baby in Red Overalls
Firstly, great thanks and props to Chinedu for shedding the light on this very dark problem, but I finally manged to fix it!
My small rant
"The guys that did JWebSocket went out of their way to port the whole implementation to j2me(even though they made some errors with reading the byte 0xff)." here had me stomped when I had to redo the codebase to work exclusively with websockets. The problem was figuring where the start(0x00) and end(0xff) of the json websocket packets were. And since j2me was finding it almost impossible to read these two values, Chinedu found out something based on this documentation here
To break it down, this is what it entails.
Here's my method:
My small rant
"The guys that did JWebSocket went out of their way to port the whole implementation to j2me(even though they made some errors with reading the byte 0xff)." here had me stomped when I had to redo the codebase to work exclusively with websockets. The problem was figuring where the start(0x00) and end(0xff) of the json websocket packets were. And since j2me was finding it almost impossible to read these two values, Chinedu found out something based on this documentation here
5.6. Data Frames
Data frames (e.g., non-control frames) are identified by opcodes where the most significant bit of the opcode is 0. Currently defined opcodes for data frames include 0x1 (Text), 0x2 (Binary). Opcodes 0x3-0x7 are reserved for further non-control frames yet to be defined. Data frames carry application-layer and/or extension-layer data. The opcode determines the interpretation of the data: Text The "Payload data" is text data encoded as UTF-8. Note that a particular text frame might include a partial UTF-8 sequence; however, the whole message MUST contain valid UTF-8. Invalid UTF-8 in reassembled messages is handled as described in Section 8.1. Binary The "Payload data" is arbitrary binary data whose interpretation is solely up to the application layer.5.7. Examples
o A single-frame unmasked text message * 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello") o A single-frame masked text message * 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains "Hello") o A fragmented unmasked text message * 0x01 0x03 0x48 0x65 0x6c (contains "Hel") * 0x80 0x02 0x6c 0x6f (contains "lo")
To break it down, this is what it entails.
- Read the incoming data stream like this b = is.read()
- Check for this value '129' which signifies the start of the package. "129 is 0x81 which is the marker for a single-frame unmasked text message"
- Read the next byte which represents the length of the packet.
- Iterate as many times as the length acquired to read the whole packet
Here's my method:
public StringBuffer readStream(InputStream is){UPDATE: This actually failed. Server underwent a major rebuild with websockets as the ONLY communication channel.
StringBuffer sb = null;
int length = -1;
try {
int b = is.read();
if (b != -1) {
if (b == 129) {//Set length
length = is.read();
}
sb = new StringBuffer();
for (int i = 0; i < length;) {
int c=is.read();
sb.append((char) c);
i++; //Increment after /*do*/
}
}
} catch (IOException ex) {
isRunning = false;
ex.printStackTrace();
}
return sb;
}
Wednesday, April 3, 2013
Error during Obfuscation
Trying to Obfuscate the saya code base, kept bumping into this error
Thanks to below link for guidance :)
http://proguard.sourceforge.net/manual/troubleshooting.html#dependency
Warning: there were 1 instances of library classes depending on program classes.The issue it seems was caused by these two classes
You must avoid such dependencies, since the program classes will
be processed, while the library classes will remain unchanged.
Error: Please correct the above warnings first.
Warning: library class com.sun.ukit.jaxp.Parser extends or implements program class org.xml.sax.LocatorThe solution was take apart the whole code and piece it together, class by class(yes-tedious I know). But finally, it worked.!
Thanks to below link for guidance :)
http://proguard.sourceforge.net/manual/troubleshooting.html#dependency
Monday, March 25, 2013
Crashes!
Am refactoring code on Netbeans. So far in less than 30 mins, 3 Java crashes with the latest one causing a kernel panic!
Tsk Tsk Tsk -- Monkeys!
Edit--4th crash, System freeze!
Tsk Tsk Tsk -- Monkeys!
Edit--4th crash, System freeze!
Sockets and/vs http
By design, http should be pretty robust but since we're talking about Java here, that's another totally different story.
The guys that did JWebSocket went out of their way to port the whole implementation to j2me(even though they made some errors with reading the byte 0xff). It's helpful that it maintains a relatively stable socket connection and will throw an Exception caught in the main app just incase a connection is lost or closed.
Current problem while using http(and which has left me at an impasse) is Timeouts(covered here). This(while I might be terribly wrong), I attribute to the fact that every http connection is atomic and involves setting up and tearing a connection instance. Add to that the fact that different j2me CLDC* implementations restrict the number of connections(sometimes to as little as 2) and the darn s60 bug I found, using sockets over http starts to look a bit more interesting.
As an implementation, Badu and the other guys server-side are working on it, but this is how I would do it:
The guys that did JWebSocket went out of their way to port the whole implementation to j2me(even though they made some errors with reading the byte 0xff). It's helpful that it maintains a relatively stable socket connection and will throw an Exception caught in the main app just incase a connection is lost or closed.
Current problem while using http(and which has left me at an impasse) is Timeouts(covered here). This(while I might be terribly wrong), I attribute to the fact that every http connection is atomic and involves setting up and tearing a connection instance. Add to that the fact that different j2me CLDC* implementations restrict the number of connections(sometimes to as little as 2) and the darn s60 bug I found, using sockets over http starts to look a bit more interesting.
As an implementation, Badu and the other guys server-side are working on it, but this is how I would do it:
- First of all, I'd like to piggy back on the current ws connection
- To do this, I would have to shape my requests into a json format e.g {"type" : "POST", "content" : {/*JSON object/array of key=>value pairs to represent the parameters*/}}
*CLDC-Connected Limited Device Configuration(where Limited really means LIMITED)
Connection Timeouts
Just noticed that the app has a number of connection Timeouts which caused the app to continue waiting on a response from a dead connection. This mean that the app would essentially wait forever for a connection that has already - less than ideal, needless to say.
Bit the bullet on that and approached the problem from another angle. I create a timertask which after a period x will check to see if the connection has been read(using a boolean flag) and if not, try and close the connection. I then have it throw a custom Exception which I wanted to catch in the main app so that I can know that the connection timed out and handle accordingly.
Unfortunately, timerTask inherits from Thread which means that there's no way to catch the thrown Exception outside it's own thread(and hence the calling method). So in essence, am stuck with a half-baked thread which cleans up pending open connections after the timeout but which cannot communicate back to the original calling Thread(and hence method) to notify it of the action.
P/S: Am still thinking some more on how to solve this problem. :(
Researching, I found that I can define a boolean value when opening the connection which should essentially throw an Exception when the timeout reached with no response from server.
HttpConnection http = (HttpConnection)Connector.open(url, Connector.READ_WRITE, true);Again with the monkey design in Java, the parameter list does not take a time parameter hence, I cannot manually define the timeout myself. Instead, I have to wait for a system-dependent timeout that no-one seems to know the real value of(ranging from 60 secs to 5 mins-depending on who you ask). I tested this and did not get any Exception thrown even though the app lost connection for quite sometime(situ 15 mins).
Bit the bullet on that and approached the problem from another angle. I create a timertask which after a period x will check to see if the connection has been read(using a boolean flag) and if not, try and close the connection. I then have it throw a custom Exception which I wanted to catch in the main app so that I can know that the connection timed out and handle accordingly.
connectionTimeOut = true;
final Timer timer = new Timer();
TimerTask timeoutTask = new TimerTask(){
public void run(){System.out.println("Timeout Expired:");
//Shutdown streams
try {
if(connectionTimeOut){
if(is != null){
//Close all streams
is.close();
}
timer.cancel(); //Shutdown timer
System.out.println("Timing out..");
//Throw exception to be handled in main class
kill = true;
throw new TimeoutException("Connection Timed Out!");
}
} catch (TimeoutException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
timer.schedule(timeoutTask, 3000);
Unfortunately, timerTask inherits from Thread which means that there's no way to catch the thrown Exception outside it's own thread(and hence the calling method). So in essence, am stuck with a half-baked thread which cleans up pending open connections after the timeout but which cannot communicate back to the original calling Thread(and hence method) to notify it of the action.
P/S: Am still thinking some more on how to solve this problem. :(
Friday, March 15, 2013
s60 Bug
I've been battling with this issue for the last couple of days. Very simply, this is what's up.
When making a POST request in an s60 device, the first request goes through but subsequent requests immediately following fail. However, a requested submitted a few seconds later will work.
After literally ripping the code apart, I found that a HTTP-400 error is what triggers the failure. Testing on another platform and language confirms that the server is not to blame here. More digging on the internet seems to point to a somewhat native issue with the underlying http methods and their hold on data/resources during and after a http request.(Even issues surrounding memory leak have been raised)
http://www.developer.nokia.com/Community/Discussion/showthread.php?102878-http-error-400-with-Content-Type
http://www.developer.nokia.com/Community/Discussion/showthread.php?67798-CHTTPFormEncoder-and-HTTP-CORE-10-Panic
When making a POST request in an s60 device, the first request goes through but subsequent requests immediately following fail. However, a requested submitted a few seconds later will work.
After literally ripping the code apart, I found that a HTTP-400 error is what triggers the failure. Testing on another platform and language confirms that the server is not to blame here. More digging on the internet seems to point to a somewhat native issue with the underlying http methods and their hold on data/resources during and after a http request.(Even issues surrounding memory leak have been raised)
http://www.developer.nokia.com/Community/Discussion/showthread.php?102878-http-error-400-with-Content-Type
http://www.developer.nokia.com/Community/Discussion/showthread.php?67798-CHTTPFormEncoder-and-HTTP-CORE-10-Panic
Tuesday, March 5, 2013
Java and it's inherent stupidity(Textfield-a case study)
That monkey called James Gosling needs to be shot off the tree that he's hanging!
The monkies who designed java and it's implementation fucked again! Spent the whole night debugging Textfield which throws an incomprehensible Uncaught exception java/lang/IllegalArgumentException.
This is because the value being passed is greater than the (compulsory)defined size of the Item.
new TextField("Group Name", name, 10, TextField.ANY);Now, you might be tempted here to get the maxSize() for TextField, but the values am getting are stupid platform suggestions that make no sense(i.e 12). So because I know any device capable of installing Saya is also capable of holding at least 100 chars in a text field, I hardcoded the limit to such
new TextField("Group Name", name, 100, TextField.ANY);But also was careful enough to make sure the dynamic content being inserted there(i.e name) does not exceed the limit.
if(name.length()>99){
name = name.substring(0, 99);
}
http://jcs.mobile-utopia.com/jcs/67140_TextField.html
public class TextField extends Item
A TextFieldis an editable text component that may be placed into a {@link Form Form}. It can be given a piece of text that is used as the initial value.
ATextFieldhas a maximum size, which is the maximum number of characters that can be stored in the object at any time (its capacity). This limit is enforced when theTextFieldinstance is constructed, when the user is editing text within theTextField, as well as when the application program calls methods on theTextFieldthat modify its contents. The maximum size is the maximum stored capacity and is unrelated to the number of characters that may be displayed at any given time. The number of characters displayed and their arrangement into rows and columns are determined by the device.
The implementation may place a boundary on the maximum size, and the maximum size actually assigned may be smaller than the application had requested. The value actually assigned will be reflected in the value returned by {@link #getMaxSize() getMaxSize()}. A defensively-written application should compare this value to the maximum size requested and be prepared to handle cases where they differ.
Saturday, March 2, 2013
Working with Arrays
So this is an example of the returned json for 'get_info' request
1. Split first. Very simple method for splitting a string into an array along defined separators
{"success":"1","info":{"created":"2012-8-30 16:47:59","country":"Ghana","user_id":"2","onlinestatus":"1","last_login_datetime":"2013-3-2 22:13:10","nickname":"badu","discover":"0"},"interests":[" cook","eat"]}Interests is an array of values. The doc specifies updating interests at such
interests - comma separated list of interestsObviously, a suitable method for converting between arrays and a comma separated string value(s) is required. Unfortunately for Java and by extension J2ME, they never bothered to think that people might implementations similar to split and join in Python. So here goes..
1. Split first. Very simple method for splitting a string into an array along defined separators
public String[] split(String original, String separator)2. Now the slightly harder part. Convert the returned array in the json response to a string. First problem here is that getting the array involves returning it as a JSONArray which apparently is not castable to a normal String Array. So this is how you do it...
{
Vector nodes=new Vector();
//Parse nodes into Vector
int index=original.indexOf(separator);
while(index>=0){
nodes.addElement(original.substring(0, index));
original=original.substring(index+separator.length());
index=original.indexOf(separator);
}
//Get the last node
nodes.addElement(original);
//Create split array
String[] result=new String[nodes.size()];
if(nodes.size()>0){
for(int loop=0; loop<nodes.size(); loop++)
{
result[loop]=(String)nodes.elementAt(loop);
}
}
return result;
}//--End of split()
//Convert JSONArray to String ArrayThis is followed by the join method which I implemented as such:
JSONArray key_array = json.getJSONArray("interests");
String[] key_attributes = new String[key_array.length()];
for(int i=0; i<key_array.length(); i++){
key_attributes[i] = key_array.getString(i);
}
public String join(String[] array, char separator){Now calling the method join, you MUST remember to provide the separator as a character. In java, it's not easy to type out a character variable in code and instead, one has to go around like this:
//Catch empty array
if (array == null) return null;
if(array.length == 0) return null;
String joined_string = "";
for(int i=0; i<array.length;){
joined_string += array[i] + separator;
i++; //Don't forget to increment
}
//Truncate trailing seprator
return joined_string.substring(0, joined_string.length()-1);
}//--End of join()
String separator = ",";There you go folks, all done with Java's usual unfriendly long way of getting simple things done.!
interests = join(key_attributes, separator.charAt(0));
Wednesday, February 27, 2013
==, .equals(), compareTo(), and compare()
This gave me hell..!
Comparing Object references with the
Comparing Object values with the
Equality comparison: One way for primitives, Four ways for objects
| Comparison | Primitives | Objects |
|---|---|---|
a == b, a != b | Equal values | Compares references, not values. The use of == with object references is generally limited to the following:
|
a.equals(b) | N/A | Compares values for equality. Because this method is defined in the Object class, from which all other classes are derived, it's automatically defined for every class. However, it doesn't perform an intelligent comparison for most classes unless the class overrides it. It has been defined in a meaningful way for most Java core classes. If it's not defined for a (user) class, it behaves the same as ==.
It turns out that defining
equals() isn't trivial; in fact it's moderately hard to get it right, especially in the case of subclasses. The best treatment of the issues is in Horstmann's Core Java Vol 1. [TODO: Add explanation and example] |
a.compareTo(b) | N/A | Comparable interface. Compares values and returns an int which tells if the values compare less than, equal, or greater than. If your class objects have a natural order, implement theComparable<T> interface and define this method. All Java classes that have a natural ordering implement this (String, Double, BigInteger, ...). |
compare(a, b) | N/A | Comparator interface. Compares values of two objects. This is implemented as part of theComparator<T> interface, and the typical use is to define one or more small utility classes that implement this, to pass to methods such as sort() or for use by sorting data structures such as TreeMap and TreeSet. You might want to create a Comparator object for the following.
If your class objects have one natural sorting order, you may not need this.
|
Comparing Object references with the == and != Operators
The two operators that can be used with object references are comparing for equality (
==) and inequality (!=). These operators compare two values to see if they refer to the same object. Although this comparison is very fast, it is often not what you want.
Usually you want to know if the objects have the same value, and not whether two objects are a reference to the same object. For example,
if (name == "Mickey Mouse") // Legal, but ALMOST SURELY WRONG
This is true only if
name is a reference to the same object that "Mickey Mouse" refers to. This will be false if the String in namewas read from input or computed (by putting strings together or taking the substring), even though name really does have exactly those characters in it.
Many classes (eg,
String) define the equals() method to compare the values of objects.
Comparing Object values with the equals() Method
Use the
equals() method to compare object values. The equals() method returns a boolean value. The previous example can be fixed by writing:if (name.equals("Mickey Mouse")) // Compares values, not references.
Because the
equals() method makes a == test first, it can be fairly fast when the objects are identical. It only compares the values if the two references are not identical.Other comparisons - Comparable<T> interface
The
equals method and == and != operators test for equality/inequality, but do not provide a way to test for relative values. Some classes (eg, String and other classes with a natural ordering) implement the Comparable<T> interface, which defines a compareTomethod. You will want to implement Comparable<T> in your class if you want to use it with Collections.sort() or Arrays.sort() methods.Defining a Comparator object
As described in the table above on
compare(), you can create Comparators to sort any arbitrary way for any class. For example, the String class defines the CASE_INSENSITIVE_ORDER comparator.If you override equals, you should also override hashCode()
Overriding
hashCode(). The hashCode() method of a class is used for hashing in library data structures such as HashSet andHashMap. If you override equals(), you should override hashCode() or your class will not work correctly in these (and some other) data structures.Shouldn't .equals and .compareTo produce same result?
The general advice is that if
a.equals(b) is true, then a.compareTo(b) == 0 should also be true. Curiously, BigDecimalviolates this. Look at the Java API documentation for an explanation of the difference. This seems wrong, although their implementation has some plausibility.Other comparison methods
String has the specialized
equalsIgnoreCase() and compareToIgnoreCase(). String also supplies the constantString.CASE_INSENSITIVE_ORDER Comparator.The === operator (Doesn't exist - yet?)
Comparing objects is somewhat awkward, so a === operator has been proposed. One proposal is that
a === b would be the same as ((a == b) || ((a != null) && a.equals(b)))Common Errors
- Using == instead of
equals()with Objects - When you want to compare objects, you need to know whether you should use
==to see if they are the same object, orequals()to see if they may be a different object, but have the same value. This kind of error can be very hard to find.
References
- Ragenwald's blog entry What does Barbara Liskov have to say about Equality in Java? and the responses touch on all the equality issues.
Copyleft 2007 Fred Swartz MIT License
Tuesday, February 26, 2013
Sinister Trap-Json Object vs Json Array
Badu made a change server side that allowed me to proceed with the default sync of contacts by uploading a json array to the server. 4 hrs later and I was racking my brain why his server kept returning a 500(Internal Server Error) response until I found out that I was submitting a json object instead of array.
What's the difference?
Json object is demarcated by {} while json array is demarcated by [ ]. Yeah, it really was that trivial.
Here's the bit of code that I used to change my object to array
What's the difference?
Json object is demarcated by {} while json array is demarcated by [ ]. Yeah, it really was that trivial.
Here's the bit of code that I used to change my object to array
JSONArray json_array = new JSONArray();
json_array.put(json);
String params = "action=sync&phone="+phone+"&contacts="+json_array.toString();
Subscribe to:
Comments (Atom)